Skip to content

Commit 2be63d6

Browse files
committed
Add cli publish function
1 parent 9fc79b3 commit 2be63d6

File tree

6 files changed

+185
-28
lines changed

6 files changed

+185
-28
lines changed

npm-packages/cli/source/cli.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { flags } from './lib/cli-flags.js';
99
const cli = meow(
1010
`\
1111
Usage
12-
pulse [commands] [flags]
12+
pulse [command] [flags]
1313
1414
Commands
1515
${Object.entries(commandsManual)

npm-packages/cli/source/components/commands/login.tsx

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import TextInput from 'ink-text-input';
77
import Spinner from 'ink-spinner';
88
import os from 'os';
99
import path from 'path';
10-
import {getToken, isTokenInEnv, saveToken} from '../../lib/token.js';
10+
import {
11+
checkToken,
12+
getToken,
13+
isTokenInEnv,
14+
saveToken,
15+
} from '../../lib/token.js';
1116
import {Item} from '../../lib/types.js';
1217

1318
type LoginMethod = 'token' | 'flow';
@@ -17,6 +22,7 @@ export default function Login({cli}: {cli: Result<Flags>}) {
1722
undefined,
1823
);
1924

25+
const [isShowLoginMethod, setIsShowLoginMethod] = useState(false);
2026
const [isMethodSelected, setIsMethodSelected] = useState(false);
2127
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
2228
const [isAuthenticated, setIsAuthenticated] = useState(false);
@@ -43,6 +49,8 @@ export default function Login({cli}: {cli: Result<Flags>}) {
4349
// Check login method
4450
useEffect(() => {
4551
const savedToken = getToken();
52+
setIsShowLoginMethod(!savedToken && !cli.flags.token && !cli.flags.flow);
53+
4654
if (savedToken) {
4755
setLoginMethod('token');
4856
setToken(savedToken);
@@ -56,22 +64,6 @@ export default function Login({cli}: {cli: Result<Flags>}) {
5664

5765
// Check token validity
5866
useEffect(() => {
59-
async function checkToken(token: string) {
60-
const res = await fetch('https://pulse-editor.com/api/api-keys/check', {
61-
body: JSON.stringify({token}),
62-
headers: {
63-
'Content-Type': 'application/json',
64-
},
65-
method: 'POST',
66-
});
67-
68-
if (res.status === 200) {
69-
return true;
70-
} else {
71-
return false;
72-
}
73-
}
74-
7567
// Only check token validity when it is set
7668
if (loginMethod === 'token' && token.length > 0) {
7769
checkToken(token).then(isValid => {
@@ -89,7 +81,7 @@ export default function Login({cli}: {cli: Result<Flags>}) {
8981

9082
return (
9183
<>
92-
{!cli.flags.token && !cli.flags.flow && (
84+
{isShowLoginMethod && (
9385
<>
9486
<Text>Login to the Pulse Editor Platform</Text>
9587
<SelectInput

npm-packages/cli/source/components/commands/logout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function Logout({cli}: {cli: Result<Flags>}) {
99
const [isLoggedOut, setIsLoggedOut] = useState(false);
1010

1111
useEffect(() => {
12-
saveToken('');
12+
saveToken(undefined);
1313
setIsLoggedOut(true);
1414
}, []);
1515

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,162 @@
1-
import React from 'react';
2-
import {Text} from 'ink';
1+
import React, {useEffect, useState} from 'react';
2+
import {Box, Text} from 'ink';
33
import {Result} from 'meow';
44
import {Flags} from '../../lib/cli-flags.js';
5+
import {checkToken, getToken} from '../../lib/token.js';
6+
import Spinner from 'ink-spinner';
7+
import fs from 'fs';
8+
import {$} from 'execa';
9+
import {cwd} from 'process';
510

611
export default function Publish({cli}: {cli: Result<Flags>}) {
12+
const [isInProjectDir, setIsInProjectDir] = useState(false);
13+
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
14+
const [isAuthenticated, setIsAuthenticated] = useState(false);
15+
const [isBuilding, setIsBuilding] = useState(false);
16+
const [isBuildingError, setIsBuildingError] = useState(false);
17+
const [isPublishing, setIsPublishing] = useState(false);
18+
const [isPublishingError, setIsPublishingError] = useState(false);
19+
const [isPublished, setIsPublished] = useState(false);
20+
21+
useEffect(() => {
22+
// Check if the current dir contains pulse.config.ts
23+
const currentDir = process.cwd();
24+
const pulseConfigPath = `${currentDir}/pulse.config.ts`;
25+
if (fs.existsSync(pulseConfigPath)) {
26+
setIsInProjectDir(true);
27+
}
28+
}, []);
29+
30+
// Check if the user is authenticated
31+
useEffect(() => {
32+
async function checkAuth() {
33+
const token = getToken();
34+
if (token) {
35+
const isValid = await checkToken(token);
36+
if (isValid) {
37+
setIsAuthenticated(true);
38+
}
39+
}
40+
setIsCheckingAuth(false);
41+
}
42+
43+
if (isInProjectDir) {
44+
checkAuth();
45+
}
46+
}, [isInProjectDir]);
47+
48+
// Build the extension
49+
useEffect(() => {
50+
async function buildExtension() {
51+
try {
52+
setIsBuilding(true);
53+
await $`npm run build`;
54+
// Zip the dist folder
55+
await $({cwd: 'dist'})`zip -r ../node_modules/@pulse-editor/dist.zip *`;
56+
} catch (error) {
57+
setIsBuildingError(true);
58+
return;
59+
} finally {
60+
setIsBuilding(false);
61+
}
62+
63+
await publishExtension();
64+
}
65+
66+
async function publishExtension() {
67+
setIsPublishing(true);
68+
69+
// Upload the zip file to the server
70+
try {
71+
const formData = new FormData();
72+
const buffer = fs.readFileSync('./node_modules/@pulse-editor/dist.zip');
73+
const blob = new Blob([buffer], {
74+
type: 'application/zip',
75+
});
76+
const file = new File([blob], 'dist.zip', {
77+
type: 'application/zip',
78+
});
79+
formData.append('file', file, 'dist.zip');
80+
81+
// Send the file to the server
82+
const res = await fetch(
83+
'https://pulse-editor.com/api/extension/publish',
84+
{
85+
method: 'POST',
86+
headers: {
87+
Authorization: `Bearer ${getToken()}`,
88+
},
89+
body: formData,
90+
},
91+
);
92+
93+
if (res.status === 200) {
94+
setIsPublished(true);
95+
} else {
96+
setIsPublishingError(true);
97+
}
98+
} catch (error) {
99+
console.error('Error uploading the file:', error);
100+
setIsPublishingError(true);
101+
return;
102+
} finally {
103+
setIsPublishing(false);
104+
}
105+
}
106+
107+
if (isAuthenticated) {
108+
buildExtension();
109+
}
110+
}, [isAuthenticated]);
111+
7112
return (
8-
<Text>
9-
Publish Pulse Editor Extension in current directory to the Pulse Editor
10-
Platform
11-
</Text>
113+
<>
114+
{!isInProjectDir ? (
115+
<Text color={'redBright'}>
116+
⛔ The current directory does not contain a Pulse Editor project.
117+
</Text>
118+
) : isCheckingAuth ? (
119+
<Box>
120+
<Spinner type="dots" />
121+
<Text> Checking authentication...</Text>
122+
</Box>
123+
) : isAuthenticated ? (
124+
<>
125+
{isBuilding && (
126+
<Box>
127+
<Spinner type="dots" />
128+
<Text> Building...</Text>
129+
</Box>
130+
)}
131+
{isBuildingError && (
132+
<Text color={'redBright'}>
133+
❌ Error building the extension. Please run `npm run build` to see
134+
the error.
135+
</Text>
136+
)}
137+
{isPublishing && (
138+
<Box>
139+
<Spinner type="dots" />
140+
<Text> Publishing...</Text>
141+
</Box>
142+
)}
143+
{isPublishingError && (
144+
<Text color={'redBright'}>❌ Failed to publish extension.</Text>
145+
)}
146+
{isPublished && (
147+
<Text color={'greenBright'}>
148+
✅ Extension published successfully.
149+
</Text>
150+
)}
151+
</>
152+
) : (
153+
<Text>
154+
You are not authenticated or your access token is invalid. Publishing
155+
to Extension Marketplace is in Beta access. Please visit
156+
<Text color={'blueBright'}> https://pulse-editor.com/beta </Text>to
157+
apply for Beta access.
158+
</Text>
159+
)}
160+
</>
12161
);
13162
}

npm-packages/cli/source/lib/manual.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const help = `\
33
`;
44

55
const chat = `\
6-
chat [message] Chat with the Pulse Editor AI assistant.
6+
chat [message] (WIP) Chat with the Pulse Editor AI assistant.
77
88
Flags:
99
--interactive, -i

npm-packages/cli/source/lib/token.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import os from 'os';
22
import path from 'path';
33
import fs from 'fs';
44

5-
export function saveToken(token: string) {
5+
export function saveToken(token: string | undefined) {
66
// Save the token to .pulse-editor/config.json in user home directory
77
const configDir = path.join(os.homedir(), '.pulse-editor');
88
const configFile = path.join(configDir, 'config.json');
@@ -44,3 +44,19 @@ export function isTokenInEnv() {
4444
}
4545
return false;
4646
}
47+
48+
export async function checkToken(token: string) {
49+
const res = await fetch('https://pulse-editor.com/api/api-keys/check', {
50+
body: JSON.stringify({token}),
51+
headers: {
52+
'Content-Type': 'application/json',
53+
},
54+
method: 'POST',
55+
});
56+
57+
if (res.status === 200) {
58+
return true;
59+
} else {
60+
return false;
61+
}
62+
}

0 commit comments

Comments
 (0)