@pulse-editor/cli 0.1.1-beta.9 → 0.1.3
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/app.js +4 -1
- package/dist/components/commands/build.js +18 -33
- package/dist/components/commands/code.d.ts +5 -0
- package/dist/components/commands/code.js +342 -0
- package/dist/components/commands/create.js +38 -12
- package/dist/components/commands/dev.js +35 -8
- package/dist/components/commands/login.d.ts +2 -2
- package/dist/components/commands/login.js +110 -26
- package/dist/components/commands/preview.js +50 -11
- package/dist/components/commands/publish.js +23 -37
- package/dist/components/commands/{start copy.d.ts → skill.d.ts} +1 -1
- package/dist/components/commands/skill.js +230 -0
- package/dist/components/commands/{preview copy.d.ts → upgrade.d.ts} +1 -1
- package/dist/components/commands/upgrade.js +53 -0
- package/dist/lib/backend/publish-app.d.ts +1 -0
- package/dist/lib/backend/publish-app.js +26 -0
- package/dist/lib/backend-url.d.ts +1 -0
- package/dist/lib/backend-url.js +3 -0
- package/dist/lib/cli-flags.d.ts +22 -0
- package/dist/lib/cli-flags.js +22 -0
- package/dist/lib/manual.js +40 -0
- package/dist/lib/server/express.js +81 -41
- package/dist/lib/server/preview/backend/load-remote.cjs +28 -18
- package/dist/lib/server/utils.js +3 -3
- package/dist/lib/token.js +2 -3
- package/dist/lib/webpack/compile.d.ts +2 -0
- package/dist/lib/webpack/compile.js +30 -0
- package/dist/lib/webpack/configs/mf-client.d.ts +3 -0
- package/dist/lib/webpack/configs/mf-client.js +184 -0
- package/dist/lib/webpack/configs/mf-server.d.ts +2 -0
- package/dist/lib/webpack/configs/mf-server.js +463 -0
- package/dist/lib/webpack/configs/preview.d.ts +3 -0
- package/dist/lib/webpack/configs/preview.js +117 -0
- package/dist/lib/webpack/configs/utils.d.ts +10 -0
- package/dist/lib/webpack/configs/utils.js +172 -0
- 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.d.ts +1 -0
- package/dist/lib/webpack/webpack-config.js +24 -0
- package/dist/lib/webpack/webpack.config.d.ts +2 -0
- package/dist/lib/webpack/webpack.config.js +527 -0
- package/package.json +31 -20
- package/readme.md +7 -1
- package/dist/components/commands/preview copy.js +0 -14
- package/dist/components/commands/start copy.js +0 -14
- package/dist/lib/deps.d.ts +0 -1
- package/dist/lib/deps.js +0 -5
- package/dist/lib/node_module_bin.d.ts +0 -1
- package/dist/lib/node_module_bin.js +0 -7
- package/dist/lib/server/preview/backend/index.d.ts +0 -1
- package/dist/lib/server/preview/backend/index.js +0 -23
package/dist/app.js
CHANGED
|
@@ -12,6 +12,9 @@ import Build from './components/commands/build.js';
|
|
|
12
12
|
import Preview from './components/commands/preview.js';
|
|
13
13
|
import Start from './components/commands/start.js';
|
|
14
14
|
import Clean from './components/commands/clean.js';
|
|
15
|
+
import Upgrade from './components/commands/upgrade.js';
|
|
16
|
+
import Skill from './components/commands/skill.js';
|
|
17
|
+
import Code from './components/commands/code.js';
|
|
15
18
|
export default function App({ cli }) {
|
|
16
19
|
const [command, setCommand] = useState(undefined);
|
|
17
20
|
if (cli.flags.stage) {
|
|
@@ -24,5 +27,5 @@ export default function App({ cli }) {
|
|
|
24
27
|
const cmd = cli.input[0] ?? 'help';
|
|
25
28
|
setCommand(cmd);
|
|
26
29
|
}, [cli.input]);
|
|
27
|
-
return (_jsxs(_Fragment, { children: [cli.flags.stage && (_jsx(Text, { color: 'yellow', children: "\u26A0\uFE0F
|
|
30
|
+
return (_jsxs(_Fragment, { children: [cli.flags.stage && (_jsx(Text, { color: 'yellow', children: "\u26A0\uFE0F You are in development mode." })), command === 'help' ? (_jsx(Help, { cli: cli })) : command === 'chat' ? (_jsx(Chat, { cli: cli })) : command === 'login' ? (_jsx(Login, { cli: cli })) : command === 'logout' ? (_jsx(Logout, { cli: cli })) : command === 'publish' ? (_jsx(Publish, { cli: cli })) : command === 'create' ? (_jsx(Create, { cli: cli })) : command === 'dev' ? (_jsx(Dev, { cli: cli })) : command === 'build' ? (_jsx(Build, { cli: cli })) : command === 'preview' ? (_jsx(Preview, { cli: cli })) : command === 'start' ? (_jsx(Start, { cli: cli })) : command === 'clean' ? (_jsx(Clean, { cli: cli })) : command === 'upgrade' ? (_jsx(Upgrade, { cli: cli })) : command === 'skill' ? (_jsx(Skill, { cli: cli })) : command === 'code' ? (_jsx(Code, { cli: cli })) : (command !== undefined && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: 'redBright', children: ["Invalid command: ", command] }), _jsxs(Text, { children: ["Run ", _jsx(Text, { color: 'blueBright', children: "pulse help" }), " to see the list of available commands."] })] })))] }));
|
|
28
31
|
}
|
|
@@ -3,6 +3,7 @@ import { useEffect } from 'react';
|
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { cleanDist } from '../../lib/execa-utils/clean.js';
|
|
6
|
+
import { webpackCompile } from '../../lib/webpack/compile.js';
|
|
6
7
|
export default function Build({ cli }) {
|
|
7
8
|
useEffect(() => {
|
|
8
9
|
async function buildProd() {
|
|
@@ -23,6 +24,19 @@ export default function Build({ cli }) {
|
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
}
|
|
27
|
+
// Create node_modules/.pulse/server directory
|
|
28
|
+
if (!fs.existsSync('node_modules/.pulse/server')) {
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
await execa('mkdir node_modules\\.pulse\\server', {
|
|
31
|
+
shell: true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
await execa('mkdir -p node_modules/.pulse/server', {
|
|
36
|
+
shell: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
26
40
|
if (process.platform === 'win32') {
|
|
27
41
|
await execa('xcopy /E /I node_modules\\@pulse-editor\\cli\\dist\\lib\\server node_modules\\.pulse\\server', {
|
|
28
42
|
shell: true,
|
|
@@ -35,40 +49,11 @@ export default function Build({ cli }) {
|
|
|
35
49
|
}
|
|
36
50
|
}
|
|
37
51
|
await cleanDist();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await execa('npx webpack --mode production', {
|
|
41
|
-
stdio: 'inherit',
|
|
42
|
-
shell: true,
|
|
43
|
-
env: {
|
|
44
|
-
NODE_OPTIONS: '--import=tsx',
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
else if (buildTarget === 'client') {
|
|
49
|
-
// Start building client only
|
|
50
|
-
await execa('npx webpack --mode production', {
|
|
51
|
-
stdio: 'inherit',
|
|
52
|
-
shell: true,
|
|
53
|
-
env: {
|
|
54
|
-
NODE_OPTIONS: '--import=tsx',
|
|
55
|
-
BUILD_TARGET: 'client',
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
else if (buildTarget === 'server') {
|
|
60
|
-
// Start building server only
|
|
61
|
-
await execa('npx webpack --mode production', {
|
|
62
|
-
stdio: 'inherit',
|
|
63
|
-
shell: true,
|
|
64
|
-
env: {
|
|
65
|
-
NODE_OPTIONS: '--import=tsx',
|
|
66
|
-
BUILD_TARGET: 'server',
|
|
67
|
-
},
|
|
68
|
-
});
|
|
52
|
+
try {
|
|
53
|
+
await webpackCompile('production', buildTarget);
|
|
69
54
|
}
|
|
70
|
-
|
|
71
|
-
console.error(
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error('❌ Webpack build failed', err);
|
|
72
57
|
}
|
|
73
58
|
}
|
|
74
59
|
buildProd();
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import Spinner from "ink-spinner";
|
|
5
|
+
import TextInput from "ink-text-input";
|
|
6
|
+
import JSZip from "jszip";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { getBackendUrl } from "../../lib/backend-url.js";
|
|
10
|
+
import { checkToken, getToken } from "../../lib/token.js";
|
|
11
|
+
function readContinueData(cwd) {
|
|
12
|
+
const pkgPath = path.resolve(cwd, "package.json");
|
|
13
|
+
if (!fs.existsSync(pkgPath)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
18
|
+
const name = typeof pkg["name"] === "string" ? pkg["name"] : undefined;
|
|
19
|
+
const version = typeof pkg["version"] === "string" ? pkg["version"] : undefined;
|
|
20
|
+
const appId = typeof pkg["appId"] === "string" ? pkg["appId"] : name;
|
|
21
|
+
if (!appId || !version) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return { appId, version, name: name ?? appId };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function makeAppName(prompt) {
|
|
31
|
+
const normalized = prompt
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replaceAll(/[^a-z0-9\s-]/g, "")
|
|
34
|
+
.trim()
|
|
35
|
+
.split(/\s+/)
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.slice(0, 5)
|
|
38
|
+
.join("_");
|
|
39
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
40
|
+
return `${normalized || "pulse_app"}_${suffix}`;
|
|
41
|
+
}
|
|
42
|
+
function parseArtifact(result) {
|
|
43
|
+
if (!result) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(result);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export default function Code({ cli }) {
|
|
54
|
+
// ── Continue mode ────────────────────────────────────────────────────────
|
|
55
|
+
const continueData = useMemo(() => {
|
|
56
|
+
if (!cli.flags.continue)
|
|
57
|
+
return undefined;
|
|
58
|
+
return readContinueData(process.cwd());
|
|
59
|
+
}, [cli.flags.continue]);
|
|
60
|
+
// ── Resolved inputs ──────────────────────────────────────────────────────
|
|
61
|
+
const [appName, setAppName] = useState(() => {
|
|
62
|
+
if (cli.flags.name)
|
|
63
|
+
return cli.flags.name;
|
|
64
|
+
if (cli.flags.continue) {
|
|
65
|
+
const data = readContinueData(process.cwd());
|
|
66
|
+
return data?.name ?? undefined;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
});
|
|
70
|
+
const [appNameInput, setAppNameInput] = useState("");
|
|
71
|
+
const [prompt, setPrompt] = useState(undefined);
|
|
72
|
+
const [promptInput, setPromptInput] = useState("");
|
|
73
|
+
// ── Auth ─────────────────────────────────────────────────────────────────
|
|
74
|
+
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
75
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
76
|
+
// ── Generation state ─────────────────────────────────────────────────────
|
|
77
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
78
|
+
const [isGenerated, setIsGenerated] = useState(false);
|
|
79
|
+
const [error, setError] = useState(undefined);
|
|
80
|
+
const [statusLines, setStatusLines] = useState([]);
|
|
81
|
+
// Per-message live streaming buffers
|
|
82
|
+
const [liveBuffers, setLiveBuffers] = useState(new Map());
|
|
83
|
+
const [toolCallErrors, setToolCallErrors] = useState([]);
|
|
84
|
+
const [artifact, setArtifact] = useState(undefined);
|
|
85
|
+
const [isDownloading, setIsDownloading] = useState(false);
|
|
86
|
+
const [downloadedPath, setDownloadedPath] = useState(undefined);
|
|
87
|
+
const [downloadError, setDownloadError] = useState(undefined);
|
|
88
|
+
const token = useMemo(() => getToken(cli.flags.stage), [cli.flags.stage]);
|
|
89
|
+
// Pre-fill prompt from inline CLI args, e.g. `pulse code "my prompt"`
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const initialPrompt = cli.input.slice(1).join(" ").trim();
|
|
92
|
+
if (initialPrompt) {
|
|
93
|
+
setPrompt(initialPrompt);
|
|
94
|
+
}
|
|
95
|
+
}, [cli.input]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
async function runAuthCheck() {
|
|
98
|
+
if (!token) {
|
|
99
|
+
setIsAuthenticated(false);
|
|
100
|
+
setIsCheckingAuth(false);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const isValid = await checkToken(token, cli.flags.stage);
|
|
104
|
+
setIsAuthenticated(isValid);
|
|
105
|
+
setIsCheckingAuth(false);
|
|
106
|
+
}
|
|
107
|
+
runAuthCheck();
|
|
108
|
+
}, [token, cli.flags.stage]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
async function runCodeGeneration(userPrompt, resolvedAppName) {
|
|
111
|
+
if (!token) {
|
|
112
|
+
setError("Missing access token. Please run pulse login first.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setError(undefined);
|
|
116
|
+
setStatusLines([]);
|
|
117
|
+
setLiveBuffers(new Map());
|
|
118
|
+
setToolCallErrors([]);
|
|
119
|
+
setArtifact(undefined);
|
|
120
|
+
setIsDownloading(false);
|
|
121
|
+
setDownloadedPath(undefined);
|
|
122
|
+
setDownloadError(undefined);
|
|
123
|
+
setIsGenerating(true);
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
let didTimeout = false;
|
|
126
|
+
const timeoutId = setTimeout(() => {
|
|
127
|
+
didTimeout = true;
|
|
128
|
+
controller.abort("Generation timeout after 5 minutes");
|
|
129
|
+
}, 5 * 60 * 1000);
|
|
130
|
+
const collectedMessages = new Map();
|
|
131
|
+
// Per-message live streaming buffers
|
|
132
|
+
const localBuffers = new Map();
|
|
133
|
+
let _artifact = undefined;
|
|
134
|
+
try {
|
|
135
|
+
const response = await fetch(`${getBackendUrl(cli.flags.stage)}/api/server-function/vibe_dev_flow/latest/generate-code/v2/generate`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${token}`,
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
},
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
prompt: userPrompt,
|
|
143
|
+
...(continueData
|
|
144
|
+
? {
|
|
145
|
+
appId: continueData.appId,
|
|
146
|
+
version: continueData.version,
|
|
147
|
+
}
|
|
148
|
+
: {
|
|
149
|
+
appName: resolvedAppName,
|
|
150
|
+
}),
|
|
151
|
+
}),
|
|
152
|
+
signal: controller.signal,
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
throw new Error(`Code generation failed: ${response.status} ${response.statusText} - ${await response.text()}`);
|
|
156
|
+
}
|
|
157
|
+
if (!response.body) {
|
|
158
|
+
throw new Error("No stream returned from server.");
|
|
159
|
+
}
|
|
160
|
+
const reader = response.body.getReader();
|
|
161
|
+
const decoder = new TextDecoder();
|
|
162
|
+
let buffer = "";
|
|
163
|
+
while (true) {
|
|
164
|
+
const { value, done } = await reader.read();
|
|
165
|
+
if (done) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
buffer += decoder.decode(value, { stream: true });
|
|
169
|
+
const parts = buffer.split("\n\n");
|
|
170
|
+
buffer = parts.pop() ?? "";
|
|
171
|
+
for (const part of parts) {
|
|
172
|
+
if (!part.startsWith("data:")) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const json = part.replace(/^data:\s*/, "");
|
|
176
|
+
let message;
|
|
177
|
+
try {
|
|
178
|
+
message = JSON.parse(json);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (message.type === "creation") {
|
|
184
|
+
collectedMessages.set(message.messageId, message);
|
|
185
|
+
if (message.data.type === "notification") {
|
|
186
|
+
const text = [message.data.title, message.data.description]
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.join(": ");
|
|
189
|
+
if (text.length > 0) {
|
|
190
|
+
setStatusLines((previous) => [...previous, { text }]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (message.data.type === "toolCall") {
|
|
194
|
+
const label = message.data.title
|
|
195
|
+
? `Tool call: ${message.data.title}`
|
|
196
|
+
: "Tool call executed";
|
|
197
|
+
setStatusLines((previous) => [
|
|
198
|
+
...previous,
|
|
199
|
+
{
|
|
200
|
+
text: label,
|
|
201
|
+
messageId: message.messageId,
|
|
202
|
+
toolTitle: message.data.title,
|
|
203
|
+
},
|
|
204
|
+
]);
|
|
205
|
+
// Start tracking this tool call's buffer
|
|
206
|
+
localBuffers.set(message.messageId, message.data.description
|
|
207
|
+
? `${message.data.description}\n`
|
|
208
|
+
: "");
|
|
209
|
+
setLiveBuffers(new Map(localBuffers));
|
|
210
|
+
}
|
|
211
|
+
if (message.data.type === "artifactOutput") {
|
|
212
|
+
_artifact = parseArtifact(message.data.result);
|
|
213
|
+
setArtifact(_artifact);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
// update message
|
|
218
|
+
const existing = collectedMessages.get(message.messageId);
|
|
219
|
+
if (!existing) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const deltaResult = message.delta.result ?? "";
|
|
223
|
+
const deltaError = message.delta.error ?? "";
|
|
224
|
+
existing.data.result = (existing.data.result ?? "") + deltaResult;
|
|
225
|
+
existing.data.error = (existing.data.error ?? "") + deltaError;
|
|
226
|
+
existing.isFinal = message.isFinal;
|
|
227
|
+
// Accumulate delta into this message's own buffer
|
|
228
|
+
if (localBuffers.has(message.messageId) &&
|
|
229
|
+
deltaResult.length > 0) {
|
|
230
|
+
const current = localBuffers.get(message.messageId) ?? "";
|
|
231
|
+
localBuffers.set(message.messageId, current + deltaResult);
|
|
232
|
+
setLiveBuffers(new Map(localBuffers));
|
|
233
|
+
}
|
|
234
|
+
// Surface tool-level errors when the message is finalized
|
|
235
|
+
if (message.isFinal && existing.data.error) {
|
|
236
|
+
const errText = existing.data.error.trim();
|
|
237
|
+
if (errText.length > 0) {
|
|
238
|
+
setToolCallErrors((previous) => [...previous, errText]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
setIsGenerated(true);
|
|
245
|
+
// Download the source zip
|
|
246
|
+
if (_artifact && _artifact.appId) {
|
|
247
|
+
setIsDownloading(true);
|
|
248
|
+
try {
|
|
249
|
+
// Step 1: exchange the archive endpoint for a SAS URL
|
|
250
|
+
const sasEndpoint = `${getBackendUrl(cli.flags.stage)}/api/app/source?app=${encodeURIComponent(_artifact.appId)}`;
|
|
251
|
+
const sasResponse = await fetch(sasEndpoint, {
|
|
252
|
+
headers: {
|
|
253
|
+
Accept: "application/zip",
|
|
254
|
+
Authorization: `Bearer ${token}`,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
if (!sasResponse.ok) {
|
|
258
|
+
throw new Error(`Failed to get download link for ${_artifact.appId}: ${sasResponse.status} ${sasResponse.statusText}`);
|
|
259
|
+
}
|
|
260
|
+
const { url: sasUrl } = (await sasResponse.json());
|
|
261
|
+
console.log(`SAS URL for ${_artifact.appId}: ${sasUrl}`);
|
|
262
|
+
// Step 2: download the actual zip from the SAS URL
|
|
263
|
+
const downloadResponse = await fetch(sasUrl);
|
|
264
|
+
if (!downloadResponse.ok) {
|
|
265
|
+
throw new Error(`Failed to download archive: ${downloadResponse.status} ${downloadResponse.statusText}`);
|
|
266
|
+
}
|
|
267
|
+
const arrayBuffer = await downloadResponse.arrayBuffer();
|
|
268
|
+
// Extract zip in-memory and write files one by one
|
|
269
|
+
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
270
|
+
const destDir = process.cwd();
|
|
271
|
+
for (const [relativePath, zipEntry] of Object.entries(zip.files)) {
|
|
272
|
+
if (zipEntry.dir)
|
|
273
|
+
continue;
|
|
274
|
+
const filePath = path.join(destDir, relativePath);
|
|
275
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
276
|
+
const content = await zipEntry.async("nodebuffer");
|
|
277
|
+
fs.writeFileSync(filePath, content);
|
|
278
|
+
}
|
|
279
|
+
setDownloadedPath(destDir);
|
|
280
|
+
}
|
|
281
|
+
catch (downloadErr) {
|
|
282
|
+
setDownloadError(downloadErr instanceof Error
|
|
283
|
+
? downloadErr.message
|
|
284
|
+
: "Failed to download source archive.");
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
setIsDownloading(false);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (generationError) {
|
|
292
|
+
if (controller.signal.aborted && didTimeout) {
|
|
293
|
+
setError("Code generation timed out after 5 minutes.");
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
setError(generationError instanceof Error
|
|
297
|
+
? generationError.message
|
|
298
|
+
: "Failed to generate code.");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
clearTimeout(timeoutId);
|
|
303
|
+
setIsGenerating(false);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (isAuthenticated && prompt && appName && continueData !== null) {
|
|
307
|
+
runCodeGeneration(prompt, appName);
|
|
308
|
+
}
|
|
309
|
+
}, [prompt, appName, cli.flags.stage, isAuthenticated, token, continueData]);
|
|
310
|
+
function getLiveLines(messageId, toolTitle) {
|
|
311
|
+
const text = liveBuffers.get(messageId);
|
|
312
|
+
if (!text)
|
|
313
|
+
return { lines: [], truncated: false };
|
|
314
|
+
const all = text.split("\n").filter((l) => l.length > 0);
|
|
315
|
+
if (toolTitle === "Agent Message") {
|
|
316
|
+
return { lines: all, truncated: false };
|
|
317
|
+
}
|
|
318
|
+
const truncated = all.length > 8;
|
|
319
|
+
return { lines: all.slice(-8), truncated };
|
|
320
|
+
}
|
|
321
|
+
return (_jsx(_Fragment, { children: isCheckingAuth ? (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Checking authentication..." })] })) : !isAuthenticated ? (_jsx(Text, { color: "redBright", children: "\u26D4 You are not authenticated. Run pulse login and try again." })) : cli.flags.continue && continueData === null ? (_jsx(Text, { color: "redBright", children: "\u26D4 Could not read app name and version from package.json in the current directory. Make sure a valid package.json with \"name\", \"version\" (and optionally \"appId\") exists." })) : appName === undefined ? (
|
|
322
|
+
// Step 1 — app name
|
|
323
|
+
_jsxs(Box, { children: [_jsx(Text, { children: "App name: " }), _jsx(TextInput, { value: appNameInput, onChange: setAppNameInput, onSubmit: (value) => {
|
|
324
|
+
const trimmed = value.trim();
|
|
325
|
+
setAppName(trimmed.length > 0 ? trimmed : makeAppName(""));
|
|
326
|
+
} })] })) : prompt === undefined ? (
|
|
327
|
+
// Step 2 — prompt
|
|
328
|
+
_jsxs(Box, { children: [_jsx(Text, { children: "Describe the Pulse app you want to generate: " }), _jsx(TextInput, { value: promptInput, onChange: setPromptInput, onSubmit: (value) => {
|
|
329
|
+
const trimmed = value.trim();
|
|
330
|
+
if (trimmed.length === 0) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
setPrompt(trimmed);
|
|
334
|
+
} })] })) : (
|
|
335
|
+
// Step 3 — generating
|
|
336
|
+
_jsxs(_Fragment, { children: [cli.flags.continue && continueData && (_jsxs(Text, { dimColor: true, children: ["Continuing from v", continueData.version, " (", continueData.appId, ")"] })), _jsxs(Text, { children: ["App name: ", _jsx(Text, { color: "yellow", children: appName })] }), _jsxs(Text, { children: ["Prompt: ", _jsx(Text, { color: "cyan", children: prompt })] }), isGenerating && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Generating app..." })] })), statusLines.map((item, index) => {
|
|
337
|
+
const { lines, truncated } = item.messageId !== undefined
|
|
338
|
+
? getLiveLines(item.messageId, item.toolTitle)
|
|
339
|
+
: { lines: [], truncated: false };
|
|
340
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: index < statusLines.length - 1, children: ["\u2022 ", item.text] }), lines.length > 0 && (_jsxs(Box, { borderStyle: "round", borderColor: "gray", flexDirection: "column", paddingX: 1, children: [truncated && (_jsx(Text, { color: "gray", dimColor: true, children: "<truncated>" })), lines.map((line, lineIndex) => (_jsx(Text, { color: "gray", children: line }, lineIndex)))] }))] }, `${item.text}-${index}`));
|
|
341
|
+
}), toolCallErrors.map((err, index) => (_jsxs(Text, { color: "redBright", children: ["\u274C ", err] }, index))), error && _jsxs(Text, { color: "redBright", children: ["\u274C ", error] }), isGenerated && !error && (_jsx(Text, { color: "greenBright", children: "\u2705 Code generation completed." })), artifact?.publishedAppLink && (_jsxs(Text, { color: "greenBright", children: ["Preview: ", artifact.publishedAppLink] })), artifact?.sourceCodeArchiveLink && (_jsxs(Text, { color: "greenBright", children: ["Source (.zip): ", artifact.sourceCodeArchiveLink] })), isDownloading && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Downloading source archive..." })] })), downloadedPath && (_jsxs(Text, { color: "greenBright", children: ["Source extracted: ", downloadedPath] })), downloadError && (_jsxs(Text, { color: "redBright", children: ["\u274C Download failed: ", downloadError] }))] })) }));
|
|
342
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { Box, Text, useApp } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
5
|
import { $, execa } from 'execa';
|
|
@@ -10,9 +10,14 @@ import path from 'path';
|
|
|
10
10
|
export default function Create({ cli }) {
|
|
11
11
|
const [framework, setFramework] = useState(undefined);
|
|
12
12
|
const [projectName, setProjectName] = useState(undefined);
|
|
13
|
+
const [displayName, setDisplayName] = useState(undefined);
|
|
13
14
|
const [visibility, setVisibility] = useState(undefined);
|
|
15
|
+
const projectPath = useMemo(() => {
|
|
16
|
+
return cli.flags.path ?? projectName;
|
|
17
|
+
}, [projectName, cli]);
|
|
14
18
|
const [isShowFrameworkSelect, setIsShowFrameworkSelect] = useState(true);
|
|
15
19
|
const [isShowProjectNameInput, setIsShowProjectNameInput] = useState(false);
|
|
20
|
+
const [isShowDisplayNameInput, setIsShowDisplayNameInput] = useState(false);
|
|
16
21
|
const [isShowVisibilitySelect, setIsShowVisibilitySelect] = useState(false);
|
|
17
22
|
const [createMessage, setCreateMessage] = useState();
|
|
18
23
|
const [errorMessage, setErrorMessage] = useState();
|
|
@@ -59,13 +64,26 @@ export default function Create({ cli }) {
|
|
|
59
64
|
if (projectName) {
|
|
60
65
|
// Check if the project already exists
|
|
61
66
|
const projectPath = path.join(process.cwd(), projectName);
|
|
62
|
-
if (fs.existsSync(projectPath)
|
|
67
|
+
if (fs.existsSync(projectPath) &&
|
|
68
|
+
fs.lstatSync(projectPath).isDirectory() &&
|
|
69
|
+
fs.readdirSync(projectPath).length > 0) {
|
|
63
70
|
setErrorMessage(_jsx(Text, { color: "redBright", children: "\u274C A project with same name already exists in current path." }));
|
|
64
71
|
setTimeout(() => {
|
|
65
72
|
exit();
|
|
66
73
|
}, 0);
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
76
|
+
const displayName = cli.flags.displayName;
|
|
77
|
+
if (displayName) {
|
|
78
|
+
setDisplayName(displayName);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
setIsShowDisplayNameInput(true);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, [projectName, cli]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (displayName) {
|
|
69
87
|
const visibility = cli.flags.visibility;
|
|
70
88
|
if (visibility) {
|
|
71
89
|
setVisibility(visibility);
|
|
@@ -74,50 +92,55 @@ export default function Create({ cli }) {
|
|
|
74
92
|
setIsShowVisibilitySelect(true);
|
|
75
93
|
}
|
|
76
94
|
}
|
|
77
|
-
}, [
|
|
95
|
+
}, [displayName, cli]);
|
|
78
96
|
useEffect(() => {
|
|
79
97
|
if (visibility && projectName) {
|
|
80
98
|
createFromTemplate(projectName, visibility);
|
|
81
99
|
}
|
|
82
100
|
}, [visibility, projectName]);
|
|
83
101
|
async function createFromTemplate(name, visibility) {
|
|
102
|
+
if (!projectPath) {
|
|
103
|
+
setErrorMessage(_jsx(Text, { color: "redBright", children: "\u274C Project path is not defined." }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
84
106
|
if (framework === 'react') {
|
|
85
107
|
// Clone the template repository
|
|
86
108
|
setCreateMessage(_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Creating a new Pulse Editor app using React template..." })] }));
|
|
87
109
|
try {
|
|
88
|
-
await $ `git clone --depth 1 https://github.com/ClayPulse/pulse-app-template.git ${
|
|
110
|
+
await $ `git clone --depth 1 https://github.com/ClayPulse/pulse-app-template.git ${projectPath}`;
|
|
89
111
|
}
|
|
90
112
|
catch (error) {
|
|
91
|
-
setCreateMessage(
|
|
113
|
+
setCreateMessage(_jsxs(Text, { color: "redBright", children: ["\u274C Failed to clone the template. ", error.message] }));
|
|
92
114
|
return;
|
|
93
115
|
}
|
|
94
116
|
// Modify the package.json file to update the name
|
|
95
117
|
setCreateMessage(_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Initializing project..." })] }));
|
|
96
118
|
/* Setup pulse.config.ts */
|
|
97
|
-
const pulseConfigPath = path.join(process.cwd(),
|
|
119
|
+
const pulseConfigPath = path.join(process.cwd(), projectPath, 'pulse.config.ts');
|
|
98
120
|
let pulseConfig = fs.readFileSync(pulseConfigPath, 'utf8');
|
|
99
121
|
// Modify visibility by matching the block that starts with 'visibility:',
|
|
100
122
|
// and replacing the entire line with the new visibility value.
|
|
101
123
|
pulseConfig = pulseConfig.replace(/visibility:\s*['"`](public|unlisted|private)['"`],?/, `visibility: '${visibility}',`);
|
|
102
124
|
fs.writeFileSync(pulseConfigPath, pulseConfig);
|
|
103
125
|
/* Setup packages.json */
|
|
104
|
-
const packageJsonPath = path.join(process.cwd(),
|
|
126
|
+
const packageJsonPath = path.join(process.cwd(), projectPath, 'package.json');
|
|
105
127
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
106
128
|
packageJson.name = name.replaceAll('-', '_');
|
|
129
|
+
packageJson.displayName = displayName;
|
|
107
130
|
// Write the modified package.json back to the file
|
|
108
131
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
109
132
|
// Remove the .git directory
|
|
110
|
-
const gitDirPath = path.join(process.cwd(),
|
|
133
|
+
const gitDirPath = path.join(process.cwd(), projectPath, '.git');
|
|
111
134
|
if (fs.existsSync(gitDirPath)) {
|
|
112
135
|
fs.rmSync(gitDirPath, { recursive: true, force: true });
|
|
113
136
|
}
|
|
114
137
|
// Remove the .github directory
|
|
115
|
-
const githubDirPath = path.join(process.cwd(),
|
|
138
|
+
const githubDirPath = path.join(process.cwd(), projectPath, '.github');
|
|
116
139
|
if (fs.existsSync(githubDirPath)) {
|
|
117
140
|
fs.rmSync(githubDirPath, { recursive: true, force: true });
|
|
118
141
|
}
|
|
119
142
|
// Remove LICENSE file
|
|
120
|
-
const licenseFilePath = path.join(process.cwd(),
|
|
143
|
+
const licenseFilePath = path.join(process.cwd(), projectPath, 'LICENSE');
|
|
121
144
|
if (fs.existsSync(licenseFilePath)) {
|
|
122
145
|
fs.rmSync(licenseFilePath, { force: true });
|
|
123
146
|
}
|
|
@@ -125,7 +148,7 @@ export default function Create({ cli }) {
|
|
|
125
148
|
// Run `npm i`
|
|
126
149
|
try {
|
|
127
150
|
await execa(`npm install`, {
|
|
128
|
-
cwd: path.join(process.cwd(),
|
|
151
|
+
cwd: path.join(process.cwd(), projectPath),
|
|
129
152
|
shell: true,
|
|
130
153
|
});
|
|
131
154
|
}
|
|
@@ -136,7 +159,7 @@ export default function Create({ cli }) {
|
|
|
136
159
|
setCreateMessage(_jsx(Text, { children: "\uD83D\uDE80 Pulse Editor React app project created successfully!" }));
|
|
137
160
|
}
|
|
138
161
|
}
|
|
139
|
-
return (_jsxs(_Fragment, { children: [isShowFrameworkSelect && (_jsx(FrameworkSelect, { cli: cli, frameworkItems: frameworkItems, framework: framework, setFramework: setFramework })), isShowProjectNameInput && (_jsx(ProjectNameInput, { projectName: projectName, setProjectName: setProjectName })), isShowVisibilitySelect && (_jsx(VisibilitySelect, { visibility: visibility, setVisibility: setVisibility })), visibility !== undefined && (_jsxs(_Fragment, { children: [framework === 'react' && _jsx(_Fragment, { children: createMessage }), framework !== 'react' && (_jsx(Text, { children: "\uD83D\uDEA7 Currently not available. We'd like to invite you to work on these frameworks if you are interested in! Check out our tutorial to integrate your favorite web framework with Pulse Editor using Module Federation." }))] })), _jsx(Text, { children: errorMessage })] }));
|
|
162
|
+
return (_jsxs(_Fragment, { children: [isShowFrameworkSelect && (_jsx(FrameworkSelect, { cli: cli, frameworkItems: frameworkItems, framework: framework, setFramework: setFramework })), isShowProjectNameInput && (_jsx(ProjectNameInput, { projectName: projectName, setProjectName: setProjectName })), isShowDisplayNameInput && (_jsx(DisplayNameInput, { projectName: projectName ?? '', displayName: displayName, setDisplayName: setDisplayName })), isShowVisibilitySelect && (_jsx(VisibilitySelect, { visibility: visibility, setVisibility: setVisibility })), visibility !== undefined && (_jsxs(_Fragment, { children: [framework === 'react' && _jsx(_Fragment, { children: createMessage }), framework !== 'react' && (_jsx(Text, { children: "\uD83D\uDEA7 Currently not available. We'd like to invite you to work on these frameworks if you are interested in! Check out our tutorial to integrate your favorite web framework with Pulse Editor using Module Federation." }))] })), _jsx(Text, { children: errorMessage })] }));
|
|
140
163
|
}
|
|
141
164
|
function FrameworkSelect({ cli, frameworkItems, framework, setFramework, }) {
|
|
142
165
|
return (_jsx(_Fragment, { children: !cli.flags.framework && (_jsxs(_Fragment, { children: [_jsx(Text, { children: "\uD83D\uDEA9Create a new Pulse Editor app using your favorite web framework!" }), _jsx(SelectInput, { items: frameworkItems, onSelect: item => {
|
|
@@ -146,6 +169,9 @@ function FrameworkSelect({ cli, frameworkItems, framework, setFramework, }) {
|
|
|
146
169
|
function ProjectNameInput({ projectName, setProjectName, }) {
|
|
147
170
|
return (_jsx(_Fragment, { children: _jsxs(Box, { children: [_jsx(Text, { children: "Enter your project name: " }), _jsx(UncontrolledTextInput, { onSubmit: value => setTimeout(() => setProjectName(value), 0), focus: projectName === undefined })] }) }));
|
|
148
171
|
}
|
|
172
|
+
function DisplayNameInput({ projectName, displayName, setDisplayName, }) {
|
|
173
|
+
return (_jsx(_Fragment, { children: _jsxs(Box, { children: [_jsxs(Text, { children: ["Enter your project display name: (default: ", projectName, ")"] }), _jsx(UncontrolledTextInput, { onSubmit: value => setTimeout(() => setDisplayName(value.length > 0 ? value : projectName), 0), focus: displayName === undefined })] }) }));
|
|
174
|
+
}
|
|
149
175
|
function VisibilitySelect({ visibility, setVisibility, }) {
|
|
150
176
|
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Enter marketplace visibility for your project:" }), _jsx(SelectInput, { items: [
|
|
151
177
|
{ label: 'Public', value: 'public' },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Text } from 'ink';
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import { useEffect } from 'react';
|
|
4
3
|
import { execa } from 'execa';
|
|
5
4
|
import fs from 'fs';
|
|
6
5
|
import { getDepsBinPath } from '../../lib/execa-utils/deps.js';
|
|
7
6
|
import { cleanDist } from '../../lib/execa-utils/clean.js';
|
|
7
|
+
import { webpackCompile } from '../../lib/webpack/compile.js';
|
|
8
8
|
export default function Dev({ cli }) {
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
async function startDevServer() {
|
|
@@ -22,6 +22,19 @@ export default function Dev({ cli }) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
// Create node_modules/.pulse/server directory
|
|
26
|
+
if (!fs.existsSync('node_modules/.pulse/server')) {
|
|
27
|
+
if (process.platform === 'win32') {
|
|
28
|
+
await execa('mkdir node_modules\\.pulse\\server', {
|
|
29
|
+
shell: true,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await execa('mkdir -p node_modules/.pulse/server', {
|
|
34
|
+
shell: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
25
38
|
if (process.platform === 'win32') {
|
|
26
39
|
await execa('xcopy /E /I node_modules\\@pulse-editor\\cli\\dist\\lib\\server node_modules\\.pulse\\server', {
|
|
27
40
|
shell: true,
|
|
@@ -34,11 +47,13 @@ export default function Dev({ cli }) {
|
|
|
34
47
|
}
|
|
35
48
|
// Start dev server
|
|
36
49
|
await cleanDist();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'
|
|
50
|
+
// Start webpack in dev watch mode and watch for changes
|
|
51
|
+
const compiler = await webpackCompile('development', undefined, true);
|
|
52
|
+
// Start server with tsx
|
|
53
|
+
await execa(getDepsBinPath('tsx'), [
|
|
54
|
+
'watch',
|
|
55
|
+
'--clear-screen=false',
|
|
56
|
+
'node_modules/@pulse-editor/cli/dist/lib/server/express.js',
|
|
42
57
|
], {
|
|
43
58
|
stdio: 'inherit',
|
|
44
59
|
shell: true,
|
|
@@ -47,8 +62,20 @@ export default function Dev({ cli }) {
|
|
|
47
62
|
NODE_ENV: 'development',
|
|
48
63
|
},
|
|
49
64
|
});
|
|
65
|
+
// Handle process exit to close webpack compiler
|
|
66
|
+
process.on('SIGINT', () => {
|
|
67
|
+
if (compiler && typeof compiler.close === 'function') {
|
|
68
|
+
compiler.close(() => {
|
|
69
|
+
process.exit();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
process.exit();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
50
76
|
}
|
|
77
|
+
console.log('🚀 Starting development server...');
|
|
51
78
|
startDevServer();
|
|
52
79
|
}, []);
|
|
53
|
-
return
|
|
80
|
+
return _jsx(_Fragment, {});
|
|
54
81
|
}
|