opencloud-platform-sdk 2.0.0 → 3.0.0
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/README.md +156 -82
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +161 -0
- package/dist/cli.mjs +138 -0
- package/dist/index.d.mts +58 -65
- package/dist/index.d.ts +58 -65
- package/dist/index.js +230 -243
- package/dist/index.mjs +230 -243
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -4,11 +4,12 @@ Official SDK for **OpenCloud** - Monetize your AI apps with ease.
|
|
|
4
4
|
|
|
5
5
|
**You control the AI, we handle the payments.**
|
|
6
6
|
|
|
7
|
-
## What's New in
|
|
7
|
+
## What's New in v3.0
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
9
|
+
- **Standalone Mode** - Works on any domain, no iframe required
|
|
10
|
+
- **Popup Authentication** - Secure OAuth-style login via popup
|
|
11
|
+
- **Automatic Login** - SDK prompts for login when charging
|
|
12
|
+
- **Cross-Domain Support** - Full CORS support for external apps
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -21,30 +22,64 @@ npm install opencloud-platform-sdk
|
|
|
21
22
|
```typescript
|
|
22
23
|
import { opencloud } from 'opencloud-platform-sdk';
|
|
23
24
|
|
|
24
|
-
// 1. Initialize with your app ID
|
|
25
|
+
// 1. Initialize with your app ID (slug)
|
|
25
26
|
opencloud.init({ appId: 'your-app-slug' });
|
|
26
27
|
|
|
27
|
-
// 2.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
// 2. Charge the user (opens login popup if needed)
|
|
29
|
+
async function handleAIFeature() {
|
|
30
|
+
const result = await opencloud.charge('generate_image');
|
|
31
|
+
|
|
32
|
+
if (!result.success) {
|
|
33
|
+
if (result.error === 'CANCELLED') {
|
|
34
|
+
// User closed the login popup
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (result.error === 'INSUFFICIENT_BALANCE') {
|
|
38
|
+
// SDK already opened top-up popup
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Charge successful! Now run your AI logic
|
|
44
|
+
const image = await myAIService.generate(prompt);
|
|
45
|
+
return image;
|
|
32
46
|
}
|
|
47
|
+
```
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
## How It Works
|
|
50
|
+
|
|
51
|
+
1. User visits your app (e.g., `https://your-app.vercel.app`)
|
|
52
|
+
2. User clicks "Generate Image" (or any paid feature)
|
|
53
|
+
3. SDK checks if user has OpenCloud session
|
|
54
|
+
4. If not logged in → Opens OpenCloud login popup
|
|
55
|
+
5. User logs in → Popup closes → SDK gets session token
|
|
56
|
+
6. SDK charges user's OpenCloud wallet
|
|
57
|
+
7. Your app continues with the AI feature
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
```
|
|
60
|
+
Your App OpenCloud
|
|
61
|
+
│ │
|
|
62
|
+
│ User clicks "Generate" │
|
|
63
|
+
│ ─────────────────────────────> │
|
|
64
|
+
│ │
|
|
65
|
+
│ SDK: opencloud.charge() │
|
|
66
|
+
│ ─────────────────────────────> │
|
|
67
|
+
│ │
|
|
68
|
+
│ No session? Open popup │
|
|
69
|
+
│ <───────────────────────────── │
|
|
70
|
+
│ │
|
|
71
|
+
│ User logs in via popup │
|
|
72
|
+
│ ─────────────────────────────> │
|
|
73
|
+
│ │
|
|
74
|
+
│ Session token returned │
|
|
75
|
+
│ <───────────────────────────── │
|
|
76
|
+
│ │
|
|
77
|
+
│ Charge wallet │
|
|
78
|
+
│ ─────────────────────────────> │
|
|
79
|
+
│ │
|
|
80
|
+
│ Success! Continue... │
|
|
81
|
+
│ <───────────────────────────── │
|
|
82
|
+
│ │
|
|
48
83
|
```
|
|
49
84
|
|
|
50
85
|
## API Reference
|
|
@@ -55,91 +90,121 @@ Initialize the SDK.
|
|
|
55
90
|
|
|
56
91
|
```typescript
|
|
57
92
|
opencloud.init({
|
|
58
|
-
appId: 'your-app-slug', // Required
|
|
59
|
-
apiUrl: 'https://opencloud.app' // Optional
|
|
93
|
+
appId: 'your-app-slug', // Required - your app's slug
|
|
94
|
+
apiUrl: 'https://opencloud.app' // Optional - defaults to production
|
|
60
95
|
});
|
|
61
96
|
```
|
|
62
97
|
|
|
63
|
-
### `opencloud.
|
|
98
|
+
### `opencloud.charge(action?, metadata?)`
|
|
64
99
|
|
|
65
|
-
|
|
100
|
+
Charge the user. Opens login popup if not authenticated.
|
|
66
101
|
|
|
67
102
|
```typescript
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
const result = await opencloud.charge('chat_message', { model: 'gpt-4' });
|
|
104
|
+
|
|
105
|
+
// Result:
|
|
106
|
+
{
|
|
107
|
+
success: true,
|
|
108
|
+
charged: 0.10,
|
|
109
|
+
balance: 4.90,
|
|
110
|
+
transactionId: 'tx_abc123'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Or on error:
|
|
114
|
+
{
|
|
115
|
+
success: false,
|
|
116
|
+
error: 'INSUFFICIENT_BALANCE' | 'CANCELLED' | 'UNKNOWN'
|
|
72
117
|
}
|
|
73
118
|
```
|
|
74
119
|
|
|
75
|
-
### `opencloud.
|
|
120
|
+
### `opencloud.withCharge(fn, options)`
|
|
76
121
|
|
|
77
|
-
|
|
122
|
+
Execute a function and charge the user. Handles auth and balance checks automatically.
|
|
78
123
|
|
|
79
124
|
```typescript
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
125
|
+
const image = await opencloud.withCharge(async () => {
|
|
126
|
+
return await myAIService.generateImage(prompt);
|
|
127
|
+
}, { action: 'generate_image' });
|
|
128
|
+
```
|
|
84
129
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
130
|
+
### `opencloud.isAuthenticated()`
|
|
131
|
+
|
|
132
|
+
Check if user is logged in.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
if (opencloud.isAuthenticated()) {
|
|
136
|
+
console.log('User is logged in');
|
|
91
137
|
}
|
|
92
138
|
```
|
|
93
139
|
|
|
94
|
-
### `opencloud.
|
|
140
|
+
### `opencloud.login()`
|
|
95
141
|
|
|
96
|
-
|
|
142
|
+
Open login popup manually.
|
|
97
143
|
|
|
98
144
|
```typescript
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
145
|
+
const session = await opencloud.login();
|
|
146
|
+
if (session) {
|
|
147
|
+
console.log('Logged in as:', session.user.email);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `opencloud.logout()`
|
|
152
|
+
|
|
153
|
+
Log out the current user.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
opencloud.logout();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `opencloud.getUser()`
|
|
160
|
+
|
|
161
|
+
Get current user info (synchronous).
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const user = opencloud.getUser();
|
|
165
|
+
// { id, email, username, balance }
|
|
103
166
|
```
|
|
104
167
|
|
|
105
168
|
### `opencloud.getBalance()`
|
|
106
169
|
|
|
107
|
-
Get user's current balance.
|
|
170
|
+
Get user's current balance (async, fetches from server).
|
|
108
171
|
|
|
109
172
|
```typescript
|
|
110
173
|
const balance = await opencloud.getBalance();
|
|
111
174
|
console.log(`Balance: $${balance}`);
|
|
112
175
|
```
|
|
113
176
|
|
|
114
|
-
### `opencloud.
|
|
177
|
+
### `opencloud.canAfford()`
|
|
115
178
|
|
|
116
|
-
|
|
179
|
+
Check if user can afford to use the app.
|
|
117
180
|
|
|
118
181
|
```typescript
|
|
119
|
-
const
|
|
120
|
-
|
|
182
|
+
const canUse = await opencloud.canAfford();
|
|
183
|
+
if (!canUse) {
|
|
184
|
+
opencloud.openTopUp();
|
|
185
|
+
}
|
|
121
186
|
```
|
|
122
187
|
|
|
123
|
-
### `opencloud.
|
|
188
|
+
### `opencloud.openTopUp()`
|
|
124
189
|
|
|
125
|
-
Open
|
|
190
|
+
Open top-up popup for user to add credits.
|
|
126
191
|
|
|
127
192
|
```typescript
|
|
128
|
-
opencloud.
|
|
193
|
+
opencloud.openTopUp();
|
|
129
194
|
```
|
|
130
195
|
|
|
131
|
-
### `opencloud.
|
|
196
|
+
### `opencloud.getAppPrice()`
|
|
132
197
|
|
|
133
|
-
Get
|
|
198
|
+
Get your app's price per use.
|
|
134
199
|
|
|
135
200
|
```typescript
|
|
136
|
-
const
|
|
137
|
-
|
|
201
|
+
const price = await opencloud.getAppPrice();
|
|
202
|
+
console.log(`Price: $${price}`);
|
|
138
203
|
```
|
|
139
204
|
|
|
140
205
|
### `opencloud.isPreview()`
|
|
141
206
|
|
|
142
|
-
Check if running in development mode
|
|
207
|
+
Check if running in development mode.
|
|
143
208
|
|
|
144
209
|
```typescript
|
|
145
210
|
if (opencloud.isPreview()) {
|
|
@@ -157,35 +222,44 @@ User pays: $0.10 per use
|
|
|
157
222
|
└─ Platform: $0.015 (15%)
|
|
158
223
|
```
|
|
159
224
|
|
|
160
|
-
## Error Handling
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
try {
|
|
164
|
-
await opencloud.trackUsage({ action: 'chat' });
|
|
165
|
-
} catch (error) {
|
|
166
|
-
if (error.message === 'INSUFFICIENT_BALANCE') {
|
|
167
|
-
opencloud.requestTopUp();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
225
|
## Preview Mode
|
|
173
226
|
|
|
174
|
-
When running locally (localhost
|
|
175
|
-
- `
|
|
227
|
+
When running locally (localhost), the SDK automatically enters preview mode:
|
|
228
|
+
- `charge()` doesn't charge real money
|
|
176
229
|
- `getBalance()` returns 999.99
|
|
177
230
|
- `canAfford()` always returns true
|
|
178
231
|
|
|
179
232
|
This lets you develop without worrying about charges.
|
|
180
233
|
|
|
234
|
+
## Error Handling
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
const result = await opencloud.charge('chat');
|
|
238
|
+
|
|
239
|
+
if (!result.success) {
|
|
240
|
+
switch (result.error) {
|
|
241
|
+
case 'CANCELLED':
|
|
242
|
+
// User closed the login popup
|
|
243
|
+
showMessage('Please log in to use this feature');
|
|
244
|
+
break;
|
|
245
|
+
case 'INSUFFICIENT_BALANCE':
|
|
246
|
+
// SDK already opened top-up popup
|
|
247
|
+
showMessage('Please add credits to continue');
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
showMessage('Something went wrong');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
181
255
|
## Publishing Your App
|
|
182
256
|
|
|
183
|
-
1. Deploy your app to Vercel
|
|
257
|
+
1. Deploy your app to Vercel
|
|
184
258
|
2. Go to [opencloud.app/publish](/publish)
|
|
185
|
-
3.
|
|
259
|
+
3. Create a new app with your deployed URL
|
|
186
260
|
4. Set name, description, and **price per use**
|
|
187
|
-
5.
|
|
188
|
-
6.
|
|
261
|
+
5. Get your app slug
|
|
262
|
+
6. Use the slug in `opencloud.init({ appId: 'your-slug' })`
|
|
189
263
|
|
|
190
264
|
## TypeScript Support
|
|
191
265
|
|
|
@@ -195,8 +269,8 @@ Full TypeScript definitions included:
|
|
|
195
269
|
import {
|
|
196
270
|
opencloud,
|
|
197
271
|
OpenCloudSDK,
|
|
198
|
-
|
|
199
|
-
|
|
272
|
+
ChargeResult,
|
|
273
|
+
UserSession,
|
|
200
274
|
UserInfo
|
|
201
275
|
} from 'opencloud-platform-sdk';
|
|
202
276
|
```
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var fs = __toESM(require("fs"));
|
|
28
|
+
var path = __toESM(require("path"));
|
|
29
|
+
var VERCEL_HEADERS = {
|
|
30
|
+
headers: [
|
|
31
|
+
{
|
|
32
|
+
source: "/(.*)",
|
|
33
|
+
headers: [
|
|
34
|
+
{ key: "X-Frame-Options", value: "ALLOWALL" },
|
|
35
|
+
{ key: "Content-Security-Policy", value: "frame-ancestors *" }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
function findProjectRoot() {
|
|
41
|
+
let dir = process.cwd();
|
|
42
|
+
while (dir !== path.dirname(dir)) {
|
|
43
|
+
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
dir = path.dirname(dir);
|
|
47
|
+
}
|
|
48
|
+
return process.cwd();
|
|
49
|
+
}
|
|
50
|
+
function setupVercelJson(projectRoot) {
|
|
51
|
+
const vercelJsonPath = path.join(projectRoot, "vercel.json");
|
|
52
|
+
try {
|
|
53
|
+
let config = {};
|
|
54
|
+
if (fs.existsSync(vercelJsonPath)) {
|
|
55
|
+
const content = fs.readFileSync(vercelJsonPath, "utf-8");
|
|
56
|
+
config = JSON.parse(content);
|
|
57
|
+
console.log(" Found existing vercel.json");
|
|
58
|
+
} else {
|
|
59
|
+
console.log(" Creating vercel.json");
|
|
60
|
+
}
|
|
61
|
+
if (config.headers) {
|
|
62
|
+
const hasFrameHeaders = config.headers.some(
|
|
63
|
+
(h) => h.headers?.some(
|
|
64
|
+
(header) => header.key === "X-Frame-Options" || header.key === "Content-Security-Policy"
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
if (hasFrameHeaders) {
|
|
68
|
+
console.log(" \u2713 Headers already configured in vercel.json");
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
config.headers.push(...VERCEL_HEADERS.headers);
|
|
72
|
+
} else {
|
|
73
|
+
config.headers = VERCEL_HEADERS.headers;
|
|
74
|
+
}
|
|
75
|
+
fs.writeFileSync(vercelJsonPath, JSON.stringify(config, null, 2));
|
|
76
|
+
console.log(" \u2713 Added iframe headers to vercel.json");
|
|
77
|
+
return true;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(" \u2717 Failed to update vercel.json:", error);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function printManualInstructions() {
|
|
84
|
+
console.log(`
|
|
85
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
86
|
+
\u2502 Manual Configuration Required \u2502
|
|
87
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
88
|
+
\u2502 \u2502
|
|
89
|
+
\u2502 Add this to your vercel.json: \u2502
|
|
90
|
+
\u2502 \u2502
|
|
91
|
+
\u2502 { \u2502
|
|
92
|
+
\u2502 "headers": [ \u2502
|
|
93
|
+
\u2502 { \u2502
|
|
94
|
+
\u2502 "source": "/(.*)", \u2502
|
|
95
|
+
\u2502 "headers": [ \u2502
|
|
96
|
+
\u2502 { "key": "X-Frame-Options", "value": "ALLOWALL" },
|
|
97
|
+
\u2502 { "key": "Content-Security-Policy", \u2502
|
|
98
|
+
\u2502 "value": "frame-ancestors *" } \u2502
|
|
99
|
+
\u2502 ] \u2502
|
|
100
|
+
\u2502 } \u2502
|
|
101
|
+
\u2502 ] \u2502
|
|
102
|
+
\u2502 } \u2502
|
|
103
|
+
\u2502 \u2502
|
|
104
|
+
\u2502 Or add headers() to next.config.js: \u2502
|
|
105
|
+
\u2502 \u2502
|
|
106
|
+
\u2502 async headers() { \u2502
|
|
107
|
+
\u2502 return [{ \u2502
|
|
108
|
+
\u2502 source: '/(.*)', \u2502
|
|
109
|
+
\u2502 headers: [ \u2502
|
|
110
|
+
\u2502 { key: 'X-Frame-Options', value: 'ALLOWALL' }, \u2502
|
|
111
|
+
\u2502 { key: 'Content-Security-Policy', \u2502
|
|
112
|
+
\u2502 value: 'frame-ancestors *' } \u2502
|
|
113
|
+
\u2502 ] \u2502
|
|
114
|
+
\u2502 }]; \u2502
|
|
115
|
+
\u2502 } \u2502
|
|
116
|
+
\u2502 \u2502
|
|
117
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
118
|
+
`);
|
|
119
|
+
}
|
|
120
|
+
function main() {
|
|
121
|
+
const args = process.argv.slice(2);
|
|
122
|
+
const command = args[0];
|
|
123
|
+
console.log("\n\u{1F680} OpenCloud SDK Setup\n");
|
|
124
|
+
if (command === "setup" || !command) {
|
|
125
|
+
const projectRoot = findProjectRoot();
|
|
126
|
+
console.log(`Project root: ${projectRoot}
|
|
127
|
+
`);
|
|
128
|
+
console.log("Configuring iframe headers for OpenCloud...\n");
|
|
129
|
+
const success = setupVercelJson(projectRoot);
|
|
130
|
+
if (success) {
|
|
131
|
+
console.log(`
|
|
132
|
+
\u2705 Setup complete!
|
|
133
|
+
|
|
134
|
+
Your app is now configured to work with OpenCloud.
|
|
135
|
+
After deploying, your app will be embeddable in the OpenCloud marketplace.
|
|
136
|
+
|
|
137
|
+
Next steps:
|
|
138
|
+
1. Deploy your app: vercel deploy --prod
|
|
139
|
+
2. Publish on OpenCloud: https://opencloud.app/publish
|
|
140
|
+
`);
|
|
141
|
+
} else {
|
|
142
|
+
printManualInstructions();
|
|
143
|
+
}
|
|
144
|
+
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
145
|
+
console.log(`
|
|
146
|
+
Usage: opencloud-sdk [command]
|
|
147
|
+
|
|
148
|
+
Commands:
|
|
149
|
+
setup Configure your project for OpenCloud (default)
|
|
150
|
+
help Show this help message
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
npx opencloud-platform-sdk setup
|
|
154
|
+
npx opencloud-platform-sdk
|
|
155
|
+
`);
|
|
156
|
+
} else {
|
|
157
|
+
console.log(`Unknown command: ${command}`);
|
|
158
|
+
console.log('Run "opencloud-sdk help" for usage information.');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
main();
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
var VERCEL_HEADERS = {
|
|
7
|
+
headers: [
|
|
8
|
+
{
|
|
9
|
+
source: "/(.*)",
|
|
10
|
+
headers: [
|
|
11
|
+
{ key: "X-Frame-Options", value: "ALLOWALL" },
|
|
12
|
+
{ key: "Content-Security-Policy", value: "frame-ancestors *" }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
function findProjectRoot() {
|
|
18
|
+
let dir = process.cwd();
|
|
19
|
+
while (dir !== path.dirname(dir)) {
|
|
20
|
+
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
dir = path.dirname(dir);
|
|
24
|
+
}
|
|
25
|
+
return process.cwd();
|
|
26
|
+
}
|
|
27
|
+
function setupVercelJson(projectRoot) {
|
|
28
|
+
const vercelJsonPath = path.join(projectRoot, "vercel.json");
|
|
29
|
+
try {
|
|
30
|
+
let config = {};
|
|
31
|
+
if (fs.existsSync(vercelJsonPath)) {
|
|
32
|
+
const content = fs.readFileSync(vercelJsonPath, "utf-8");
|
|
33
|
+
config = JSON.parse(content);
|
|
34
|
+
console.log(" Found existing vercel.json");
|
|
35
|
+
} else {
|
|
36
|
+
console.log(" Creating vercel.json");
|
|
37
|
+
}
|
|
38
|
+
if (config.headers) {
|
|
39
|
+
const hasFrameHeaders = config.headers.some(
|
|
40
|
+
(h) => h.headers?.some(
|
|
41
|
+
(header) => header.key === "X-Frame-Options" || header.key === "Content-Security-Policy"
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
if (hasFrameHeaders) {
|
|
45
|
+
console.log(" \u2713 Headers already configured in vercel.json");
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
config.headers.push(...VERCEL_HEADERS.headers);
|
|
49
|
+
} else {
|
|
50
|
+
config.headers = VERCEL_HEADERS.headers;
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(vercelJsonPath, JSON.stringify(config, null, 2));
|
|
53
|
+
console.log(" \u2713 Added iframe headers to vercel.json");
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(" \u2717 Failed to update vercel.json:", error);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function printManualInstructions() {
|
|
61
|
+
console.log(`
|
|
62
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
63
|
+
\u2502 Manual Configuration Required \u2502
|
|
64
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
65
|
+
\u2502 \u2502
|
|
66
|
+
\u2502 Add this to your vercel.json: \u2502
|
|
67
|
+
\u2502 \u2502
|
|
68
|
+
\u2502 { \u2502
|
|
69
|
+
\u2502 "headers": [ \u2502
|
|
70
|
+
\u2502 { \u2502
|
|
71
|
+
\u2502 "source": "/(.*)", \u2502
|
|
72
|
+
\u2502 "headers": [ \u2502
|
|
73
|
+
\u2502 { "key": "X-Frame-Options", "value": "ALLOWALL" },
|
|
74
|
+
\u2502 { "key": "Content-Security-Policy", \u2502
|
|
75
|
+
\u2502 "value": "frame-ancestors *" } \u2502
|
|
76
|
+
\u2502 ] \u2502
|
|
77
|
+
\u2502 } \u2502
|
|
78
|
+
\u2502 ] \u2502
|
|
79
|
+
\u2502 } \u2502
|
|
80
|
+
\u2502 \u2502
|
|
81
|
+
\u2502 Or add headers() to next.config.js: \u2502
|
|
82
|
+
\u2502 \u2502
|
|
83
|
+
\u2502 async headers() { \u2502
|
|
84
|
+
\u2502 return [{ \u2502
|
|
85
|
+
\u2502 source: '/(.*)', \u2502
|
|
86
|
+
\u2502 headers: [ \u2502
|
|
87
|
+
\u2502 { key: 'X-Frame-Options', value: 'ALLOWALL' }, \u2502
|
|
88
|
+
\u2502 { key: 'Content-Security-Policy', \u2502
|
|
89
|
+
\u2502 value: 'frame-ancestors *' } \u2502
|
|
90
|
+
\u2502 ] \u2502
|
|
91
|
+
\u2502 }]; \u2502
|
|
92
|
+
\u2502 } \u2502
|
|
93
|
+
\u2502 \u2502
|
|
94
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
95
|
+
`);
|
|
96
|
+
}
|
|
97
|
+
function main() {
|
|
98
|
+
const args = process.argv.slice(2);
|
|
99
|
+
const command = args[0];
|
|
100
|
+
console.log("\n\u{1F680} OpenCloud SDK Setup\n");
|
|
101
|
+
if (command === "setup" || !command) {
|
|
102
|
+
const projectRoot = findProjectRoot();
|
|
103
|
+
console.log(`Project root: ${projectRoot}
|
|
104
|
+
`);
|
|
105
|
+
console.log("Configuring iframe headers for OpenCloud...\n");
|
|
106
|
+
const success = setupVercelJson(projectRoot);
|
|
107
|
+
if (success) {
|
|
108
|
+
console.log(`
|
|
109
|
+
\u2705 Setup complete!
|
|
110
|
+
|
|
111
|
+
Your app is now configured to work with OpenCloud.
|
|
112
|
+
After deploying, your app will be embeddable in the OpenCloud marketplace.
|
|
113
|
+
|
|
114
|
+
Next steps:
|
|
115
|
+
1. Deploy your app: vercel deploy --prod
|
|
116
|
+
2. Publish on OpenCloud: https://opencloud.app/publish
|
|
117
|
+
`);
|
|
118
|
+
} else {
|
|
119
|
+
printManualInstructions();
|
|
120
|
+
}
|
|
121
|
+
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
122
|
+
console.log(`
|
|
123
|
+
Usage: opencloud-sdk [command]
|
|
124
|
+
|
|
125
|
+
Commands:
|
|
126
|
+
setup Configure your project for OpenCloud (default)
|
|
127
|
+
help Show this help message
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
npx opencloud-platform-sdk setup
|
|
131
|
+
npx opencloud-platform-sdk
|
|
132
|
+
`);
|
|
133
|
+
} else {
|
|
134
|
+
console.log(`Unknown command: ${command}`);
|
|
135
|
+
console.log('Run "opencloud-sdk help" for usage information.');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
main();
|