@klyb/cli 0.1.19
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 +82 -0
- package/bin/klyb.js +4 -0
- package/dist/commands/deploy.js +141 -0
- package/dist/commands/dev.js +87 -0
- package/dist/commands/init.js +77 -0
- package/dist/commands/login.js +121 -0
- package/dist/credentials.js +35 -0
- package/dist/index.js +31 -0
- package/dist/simulator/client/src/SimulatorApp.js +133 -0
- package/dist/simulator/client/src/main.js +11 -0
- package/dist/simulator/client/vite.config.js +15 -0
- package/dist/templates/react-vite-ts/src/App.js +27 -0
- package/dist/templates/react-vite-ts/src/main.js +11 -0
- package/dist/templates/react-vite-ts/vite.config.js +22 -0
- package/dist/templates/templates/react-vite-ts/_env.example +12 -0
- package/dist/templates/templates/react-vite-ts/clubz.json +20 -0
- package/dist/templates/templates/react-vite-ts/index.html +13 -0
- package/dist/templates/templates/react-vite-ts/klyb.json +20 -0
- package/dist/templates/templates/react-vite-ts/package.json +24 -0
- package/dist/templates/templates/react-vite-ts/public/clubz.json +20 -0
- package/dist/templates/templates/react-vite-ts/public/preview.png +0 -0
- package/dist/templates/templates/react-vite-ts/src/App.tsx +35 -0
- package/dist/templates/templates/react-vite-ts/src/main.tsx +9 -0
- package/dist/templates/templates/react-vite-ts/tsconfig.json +37 -0
- package/dist/templates/templates/react-vite-ts/tsconfig.node.json +12 -0
- package/dist/templates/templates/react-vite-ts/vite.config.ts +25 -0
- package/package.json +37 -0
- package/src/commands/deploy.ts +155 -0
- package/src/commands/dev.ts +94 -0
- package/src/commands/init.ts +88 -0
- package/src/commands/login.ts +94 -0
- package/src/credentials.ts +36 -0
- package/src/index.ts +37 -0
- package/src/simulator/client/index.html +16 -0
- package/src/simulator/client/src/SimulatorApp.tsx +163 -0
- package/src/simulator/client/src/main.tsx +9 -0
- package/src/simulator/client/vite.config.ts +12 -0
- package/src/templates/react-vite-ts/_env.example +12 -0
- package/src/templates/react-vite-ts/index.html +13 -0
- package/src/templates/react-vite-ts/klyb.json +20 -0
- package/src/templates/react-vite-ts/package.json +24 -0
- package/src/templates/react-vite-ts/public/preview.png +0 -0
- package/src/templates/react-vite-ts/src/App.tsx +35 -0
- package/src/templates/react-vite-ts/src/main.tsx +9 -0
- package/src/templates/react-vite-ts/tsconfig.json +37 -0
- package/src/templates/react-vite-ts/tsconfig.node.json +12 -0
- package/src/templates/react-vite-ts/vite.config.ts +25 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = SimulatorApp;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
function SimulatorApp() {
|
|
39
|
+
// State
|
|
40
|
+
const [widgetUrl, setWidgetUrl] = (0, react_1.useState)('http://localhost:3001'); // Default widget port
|
|
41
|
+
const [logs, setLogs] = (0, react_1.useState)([]);
|
|
42
|
+
// Iframe ref to send responses back
|
|
43
|
+
const iframeRef = (0, react_1.useRef)(null);
|
|
44
|
+
// Initial load
|
|
45
|
+
(0, react_1.useEffect)(() => {
|
|
46
|
+
const handleMessage = (event) => {
|
|
47
|
+
const data = event.data;
|
|
48
|
+
// Filter only widget messages
|
|
49
|
+
if (data?.source !== 'clubz-widget')
|
|
50
|
+
return;
|
|
51
|
+
log('req', `${data.action} ${data.payload ? JSON.stringify(data.payload) : ''}`);
|
|
52
|
+
// Process Request
|
|
53
|
+
processBridgeRequest(data, event.source);
|
|
54
|
+
};
|
|
55
|
+
window.addEventListener('message', handleMessage);
|
|
56
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
57
|
+
}, []);
|
|
58
|
+
const log = (type, msg) => {
|
|
59
|
+
setLogs(prev => [{
|
|
60
|
+
time: new Date().toLocaleTimeString().split(' ')[0],
|
|
61
|
+
type,
|
|
62
|
+
msg
|
|
63
|
+
}, ...prev]);
|
|
64
|
+
};
|
|
65
|
+
const processBridgeRequest = (req, sourceWindow) => {
|
|
66
|
+
let responseData = null;
|
|
67
|
+
let success = true;
|
|
68
|
+
switch (req.action) {
|
|
69
|
+
case 'GET_USER':
|
|
70
|
+
responseData = { id: 'sim-user-1', name: 'Simulator User', role: 'admin' };
|
|
71
|
+
break;
|
|
72
|
+
case 'VIBRATE':
|
|
73
|
+
// Visual feedback
|
|
74
|
+
if (navigator.vibrate)
|
|
75
|
+
navigator.vibrate(200);
|
|
76
|
+
alert('đł VIBRATION TRIGGERED');
|
|
77
|
+
break;
|
|
78
|
+
case 'NAVIGATE':
|
|
79
|
+
alert(`đ§ Navigating to: ${req.payload?.route}`);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
console.warn('Unknown action', req.action);
|
|
83
|
+
}
|
|
84
|
+
// Send Response
|
|
85
|
+
const response = {
|
|
86
|
+
id: req.id,
|
|
87
|
+
success,
|
|
88
|
+
data: responseData
|
|
89
|
+
};
|
|
90
|
+
sourceWindow.postMessage(response, '*');
|
|
91
|
+
log('res', `Sent data for ${req.id}`);
|
|
92
|
+
};
|
|
93
|
+
return (<div className="flex h-screen w-full">
|
|
94
|
+
{/* Left: Device Simulator */}
|
|
95
|
+
<div className="flex-1 flex flex-col items-center justify-center bg-slate-100 p-8">
|
|
96
|
+
<div className="relative border-gray-800 bg-gray-800 border-[14px] rounded-[2.5rem] h-[600px] w-[300px] shadow-xl">
|
|
97
|
+
<div className="w-[148px] h-[18px] bg-gray-800 top-0 rounded-b-[1rem] left-1/2 -translate-x-1/2 absolute"></div>
|
|
98
|
+
<div className="h-[32px] w-[3px] bg-gray-800 absolute -start-[17px] top-[72px] rounded-s-lg"></div>
|
|
99
|
+
<div className="h-[46px] w-[3px] bg-gray-800 absolute -start-[17px] top-[124px] rounded-s-lg"></div>
|
|
100
|
+
<div className="h-[46px] w-[3px] bg-gray-800 absolute -start-[17px] top-[178px] rounded-s-lg"></div>
|
|
101
|
+
<div className="h-[64px] w-[3px] bg-gray-800 absolute -end-[17px] top-[142px] rounded-e-lg"></div>
|
|
102
|
+
<div className="rounded-[2rem] overflow-hidden w-full h-full bg-white relative">
|
|
103
|
+
{/* Iframe Content */}
|
|
104
|
+
<iframe ref={iframeRef} src={widgetUrl} className="w-full h-full border-0" title="Widget Simulator"/>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<p className="mt-4 text-slate-400 text-sm">iPhone 14 Pro Simulator</p>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{/* Right: Debug Console */}
|
|
111
|
+
<div className="w-96 bg-white border-l border-slate-200 flex flex-col">
|
|
112
|
+
<div className="p-4 border-b border-slate-200 bg-slate-50">
|
|
113
|
+
<h2 className="font-bold text-slate-700">Bridge Debugger</h2>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div className="p-4 border-b border-slate-200">
|
|
117
|
+
<label className="text-xs font-bold text-slate-500 uppercase">Widget URL</label>
|
|
118
|
+
<input type="text" value={widgetUrl} onChange={e => setWidgetUrl(e.target.value)} className="w-full mt-1 px-3 py-2 border rounded text-sm font-mono"/>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-2 bg-slate-900 font-mono text-xs">
|
|
122
|
+
{logs.length === 0 && <p className="text-slate-500 italic">Waiting for events...</p>}
|
|
123
|
+
{logs.map((l, i) => (<div key={i} className="flex gap-2">
|
|
124
|
+
<span className="text-slate-500">[{l.time}]</span>
|
|
125
|
+
<span className={l.type === 'req' ? 'text-blue-400' : 'text-green-400'}>
|
|
126
|
+
{l.type === 'req' ? 'â' : 'â'}
|
|
127
|
+
</span>
|
|
128
|
+
<span className="text-slate-300 break-all">{l.msg}</span>
|
|
129
|
+
</div>))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>);
|
|
133
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
const client_1 = __importDefault(require("react-dom/client"));
|
|
8
|
+
const SimulatorApp_1 = __importDefault(require("./SimulatorApp"));
|
|
9
|
+
client_1.default.createRoot(document.getElementById('root')).render(<react_1.default.StrictMode>
|
|
10
|
+
<SimulatorApp_1.default />
|
|
11
|
+
</react_1.default.StrictMode>);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vite_1 = require("vite");
|
|
7
|
+
const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
|
|
8
|
+
exports.default = (0, vite_1.defineConfig)({
|
|
9
|
+
plugins: [(0, plugin_react_1.default)()],
|
|
10
|
+
root: __dirname, // Ensure root is set to this directory
|
|
11
|
+
build: {
|
|
12
|
+
outDir: '../../../dist/simulator/client', // Build into the CLI dist folder
|
|
13
|
+
emptyOutDir: true
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
const sdk_1 = require("@clubz/sdk");
|
|
5
|
+
function App() {
|
|
6
|
+
const [user, setUser] = (0, react_1.useState)(null);
|
|
7
|
+
(0, react_1.useEffect)(() => {
|
|
8
|
+
// Determine user context via Bridge
|
|
9
|
+
sdk_1.bridge.getUser().then((u) => setUser(u)).catch(() => console.log('Guest mode'));
|
|
10
|
+
}, []);
|
|
11
|
+
const handleVibrate = () => {
|
|
12
|
+
sdk_1.bridge.vibrate();
|
|
13
|
+
};
|
|
14
|
+
return (<div className="w-full h-full min-h-[200px] bg-white rounded-xl p-6 border border-slate-200 flex flex-col items-center justify-center">
|
|
15
|
+
<h1 className="text-xl font-bold text-slate-800 mb-2">
|
|
16
|
+
Hello {user ? user.name : 'Guest'}!
|
|
17
|
+
</h1>
|
|
18
|
+
<p className="text-slate-500 text-center mb-4">
|
|
19
|
+
Welcome to your new Clubz Widget.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<button onClick={handleVibrate} className="px-4 py-2 bg-blue-600 text-white rounded-lg active:bg-blue-700 transition">
|
|
23
|
+
Test Vibration
|
|
24
|
+
</button>
|
|
25
|
+
</div>);
|
|
26
|
+
}
|
|
27
|
+
exports.default = App;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
const client_1 = __importDefault(require("react-dom/client"));
|
|
8
|
+
const App_tsx_1 = __importDefault(require("./App.tsx"));
|
|
9
|
+
client_1.default.createRoot(document.getElementById('root')).render(<react_1.default.StrictMode>
|
|
10
|
+
<App_tsx_1.default />
|
|
11
|
+
</react_1.default.StrictMode>);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vite_1 = require("vite");
|
|
7
|
+
const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
|
|
8
|
+
// https://vitejs.dev/config/
|
|
9
|
+
exports.default = (0, vite_1.defineConfig)({
|
|
10
|
+
plugins: [(0, plugin_react_1.default)()],
|
|
11
|
+
build: {
|
|
12
|
+
rollupOptions: {
|
|
13
|
+
output: {
|
|
14
|
+
// Ensure we get a single recognizable entry point if possible,
|
|
15
|
+
// though standard vite build is fine for the platform zipper.
|
|
16
|
+
entryFileNames: 'assets/[name].js',
|
|
17
|
+
chunkFileNames: 'assets/[name].js',
|
|
18
|
+
assetFileNames: 'assets/[name].[ext]'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
2
|
+
# Configuration Klyb Widget
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
# L'URL de l'API Klyb.
|
|
6
|
+
# Par défaut, la CLI essaiera de se connecter à http://localhost:3000 si non défini.
|
|
7
|
+
# Décommentez la ligne ci-dessous pour forcer l'environnement de Production :
|
|
8
|
+
# KLYB_API_URL=https://api.klyb.co
|
|
9
|
+
|
|
10
|
+
# Votre clé d'API Développeur Klyb
|
|
11
|
+
# Vous pouvez la générer depuis votre Espace Développeur sur klyb_comu (Onglet "Clés API")
|
|
12
|
+
KLYB_API_KEY=votre_cle_api_secrete_ici
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "My Clubz Widget",
|
|
3
|
+
"description": "A widget built for Clubz",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "widget",
|
|
6
|
+
"preview": "preview.png",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"read_community_name"
|
|
9
|
+
],
|
|
10
|
+
"config": {
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "title",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"label": "Titre du widget",
|
|
16
|
+
"default": "Hello World"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Klyb Widget</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "My Klyb Widget",
|
|
3
|
+
"description": "A widget built for Klyb",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "widget",
|
|
6
|
+
"preview": "preview.png",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"read_community_name"
|
|
9
|
+
],
|
|
10
|
+
"config": {
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "title",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"label": "Titre du widget",
|
|
16
|
+
"default": "Hello World"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-klyb-widget",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"deploy": "klyb deploy --submit",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^18.2.0",
|
|
14
|
+
"react-dom": "^18.2.0",
|
|
15
|
+
"@klyb/sdk": "latest"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^18.2.66",
|
|
19
|
+
"@types/react-dom": "^18.2.22",
|
|
20
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
21
|
+
"typescript": "^5.2.2",
|
|
22
|
+
"vite": "^5.2.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
```
|
|
2
|
+
{
|
|
3
|
+
"name": "My Clubz Widget",
|
|
4
|
+
"description": "A widget built for Clubz",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"type": "widget",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"read_community_name"
|
|
9
|
+
],
|
|
10
|
+
"config": {
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "title",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"label": "Titre du widget",
|
|
16
|
+
"default": "Hello World"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
]
|
|
20
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { bridge } from '@klyb/sdk'
|
|
3
|
+
|
|
4
|
+
function App() {
|
|
5
|
+
const [user, setUser] = useState<{ name: string } | null>(null)
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
// Determine user context via Bridge
|
|
9
|
+
bridge.getUser().then((u: any) => setUser(u)).catch(() => console.log('Guest mode'))
|
|
10
|
+
}, [])
|
|
11
|
+
|
|
12
|
+
const handleVibrate = () => {
|
|
13
|
+
bridge.vibrate();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="w-full h-full min-h-[200px] bg-white rounded-xl p-6 border border-slate-200 flex flex-col items-center justify-center">
|
|
18
|
+
<h1 className="text-xl font-bold text-slate-800 mb-2">
|
|
19
|
+
Hello {user ? user.name : 'Guest'}!
|
|
20
|
+
</h1>
|
|
21
|
+
<p className="text-slate-500 text-center mb-4">
|
|
22
|
+
Welcome to your new Klyb Widget.
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<button
|
|
26
|
+
onClick={handleVibrate}
|
|
27
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-lg active:bg-blue-700 transition"
|
|
28
|
+
>
|
|
29
|
+
Test Vibration
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default App
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
/* Bundler mode */
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noEmit": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
/* Linting */
|
|
20
|
+
"strict": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"preserveSymlinks": true
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"exclude": [
|
|
30
|
+
"node_modules"
|
|
31
|
+
],
|
|
32
|
+
"references": [
|
|
33
|
+
{
|
|
34
|
+
"path": "./tsconfig.node.json"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
// https://vitejs.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
build: {
|
|
8
|
+
rollupOptions: {
|
|
9
|
+
output: {
|
|
10
|
+
// Ensure we get a single recognizable entry point if possible,
|
|
11
|
+
// though standard vite build is fine for the platform zipper.
|
|
12
|
+
entryFileNames: 'assets/[name].js',
|
|
13
|
+
chunkFileNames: 'assets/[name].js',
|
|
14
|
+
assetFileNames: 'assets/[name].[ext]'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
resolve: {
|
|
19
|
+
preserveSymlinks: true,
|
|
20
|
+
dedupe: ['react', 'react-dom']
|
|
21
|
+
},
|
|
22
|
+
optimizeDeps: {
|
|
23
|
+
exclude: ['@klyb/sdk']
|
|
24
|
+
}
|
|
25
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@klyb/cli",
|
|
3
|
+
"version": "0.1.19",
|
|
4
|
+
"bin": {
|
|
5
|
+
"klyb": "bin/klyb.js"
|
|
6
|
+
},
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc && cp -r src/templates dist/templates",
|
|
10
|
+
"dev": "tsc -w"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@types/archiver": "^7.0.0",
|
|
17
|
+
"@types/cross-spawn": "^6.0.6",
|
|
18
|
+
"@types/express": "^5.0.6",
|
|
19
|
+
"@types/fs-extra": "^11.0.4",
|
|
20
|
+
"archiver": "^7.0.1",
|
|
21
|
+
"axios": "^1.13.4",
|
|
22
|
+
"chalk": "^4.1.2",
|
|
23
|
+
"commander": "^11.0.0",
|
|
24
|
+
"cross-spawn": "^7.0.6",
|
|
25
|
+
"dotenv": "^17.2.4",
|
|
26
|
+
"express": "^5.2.1",
|
|
27
|
+
"form-data": "^4.0.5",
|
|
28
|
+
"fs-extra": "^11.3.3",
|
|
29
|
+
"inquirer": "^8.0.0",
|
|
30
|
+
"open": "^11.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/inquirer": "^9.0.9",
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import archiver from 'archiver';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import FormData from 'form-data';
|
|
8
|
+
import { getCredentials } from '../credentials';
|
|
9
|
+
|
|
10
|
+
export async function deployCommand() {
|
|
11
|
+
console.log(chalk.blue('đ Deploying Widget to Klyb...'));
|
|
12
|
+
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const configPath = path.join(cwd, 'klyb.json');
|
|
15
|
+
|
|
16
|
+
// On vérifie qu'on est bien à la racine d'un projet de widget (présence du klyb.json)
|
|
17
|
+
if (!fs.existsSync(configPath)) {
|
|
18
|
+
console.error(chalk.red('â klyb.json not found. Are you in a Klyb project?'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const config = await fs.readJson(configPath);
|
|
22
|
+
console.log(chalk.dim(` Project: ${config.name} v${config.version}`));
|
|
23
|
+
|
|
24
|
+
// On compile le code source du projet (TypeScript, React, etc.)
|
|
25
|
+
console.log(chalk.yellow('\nđŠ Building project...'));
|
|
26
|
+
try {
|
|
27
|
+
execSync('npm run build', { stdio: 'inherit' });
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(chalk.red('â Build failed. Fix errors and try again.'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// On prépare l'archive contenant uniquement les fichiers compilés (le dossier dist)
|
|
34
|
+
console.log(chalk.yellow('\nđ€ Zipping assets...'));
|
|
35
|
+
const distDir = path.join(cwd, 'dist');
|
|
36
|
+
const zipPath = path.join(cwd, 'bundle.zip');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(distDir)) {
|
|
39
|
+
console.error(chalk.red('â dist/ directory not found after build.'));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await zipDirectory(distDir, zipPath, { source: configPath, name: 'klyb.json' });
|
|
44
|
+
console.log(chalk.green(` Bundle created: ${getSize(zipPath)}`));
|
|
45
|
+
|
|
46
|
+
// Ătape finale : on envoie tout ça vers la plateforme Klyb
|
|
47
|
+
console.log(chalk.yellow('\nâïž Uploading to Klyb Registry...'));
|
|
48
|
+
|
|
49
|
+
// Récupération des accÚs enregistrés lors du `klyb login`
|
|
50
|
+
const credentials = getCredentials();
|
|
51
|
+
const apiUrl = credentials?.apiUrl || process.env.KLYB_API_URL || 'http://localhost:3000';
|
|
52
|
+
|
|
53
|
+
if (!credentials?.token) {
|
|
54
|
+
console.error(chalk.red('â Not authenticated.'));
|
|
55
|
+
console.error(chalk.yellow(' Run: ') + chalk.cyan('klyb login'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const token = credentials.token;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const isSubmission = process.argv.includes('--submit');
|
|
63
|
+
const status = isSubmission ? 'published' : 'draft';
|
|
64
|
+
|
|
65
|
+
const form = new FormData();
|
|
66
|
+
form.append('file', fs.createReadStream(zipPath));
|
|
67
|
+
form.append('manifest', JSON.stringify(config));
|
|
68
|
+
// Appel direct Ă l'API pour lancer la publication
|
|
69
|
+
const deployUrl = `${apiUrl}/api/widget-library/developer/deploy`;
|
|
70
|
+
console.log(chalk.blue(`\nđ Deploying to ${deployUrl}...`));
|
|
71
|
+
|
|
72
|
+
const response = await axios.post(deployUrl, form, {
|
|
73
|
+
headers: {
|
|
74
|
+
...form.getHeaders(),
|
|
75
|
+
'Authorization': `Bearer ${token}`
|
|
76
|
+
},
|
|
77
|
+
maxContentLength: Infinity,
|
|
78
|
+
maxBodyLength: Infinity
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(chalk.green('\nâ
Deployment Successful!'));
|
|
82
|
+
console.log(chalk.cyan(` Widget ID: ${response.data.widgetId}`));
|
|
83
|
+
console.log(chalk.cyan(` Version: ${response.data.version}`));
|
|
84
|
+
console.log(chalk.dim(` Download URL: ${response.data.url}`));
|
|
85
|
+
|
|
86
|
+
// On s'assure que le projet local garde bien l'identifiant définitif du widget
|
|
87
|
+
if (config.id !== response.data.widgetId) {
|
|
88
|
+
config.id = response.data.widgetId;
|
|
89
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
90
|
+
console.log(chalk.dim(` (Updated local klyb.json with new ID)`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (isSubmission) {
|
|
94
|
+
console.log(chalk.magenta(' đ Submitted for validation (Status: Pending)'));
|
|
95
|
+
console.log(chalk.dim(' An admin will review your widget shortly.'));
|
|
96
|
+
} else {
|
|
97
|
+
console.log(chalk.dim(' Saved as Draft. PRO TIP: Use --submit to send for validation.'));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(chalk.dim(' Available in your "Submissions" tab.'));
|
|
101
|
+
|
|
102
|
+
// On supprime le fichier zip qui ne sert plus Ă rien
|
|
103
|
+
await fs.remove(zipPath);
|
|
104
|
+
|
|
105
|
+
} catch (error: any) {
|
|
106
|
+
console.error(chalk.red('\nâ Upload failed'));
|
|
107
|
+
if (error.response) {
|
|
108
|
+
console.error(chalk.red(` Server Error: ${error.response.status} ${error.response.statusText}`));
|
|
109
|
+
console.error(chalk.yellow(` Response Data: ${JSON.stringify(error.response.data, null, 2)}`));
|
|
110
|
+
} else if (error.request) {
|
|
111
|
+
console.error(chalk.red(` No response from server`));
|
|
112
|
+
console.error(chalk.yellow(` API URL: ${apiUrl}`));
|
|
113
|
+
console.error(chalk.dim(` Check if the API is running on ${apiUrl}`));
|
|
114
|
+
} else {
|
|
115
|
+
console.error(chalk.red(` Error: ${error.message}`));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// On n'oublie pas de supprimer le fichier zip mĂȘme si l'envoi a Ă©chouĂ©
|
|
119
|
+
try {
|
|
120
|
+
if (await fs.pathExists(zipPath)) {
|
|
121
|
+
await fs.remove(zipPath);
|
|
122
|
+
}
|
|
123
|
+
} catch (cleanupError) {
|
|
124
|
+
// Ignore cleanup errors
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function zipDirectory(source: string, out: string, extraFile?: { source: string, name: string }): Promise<void> {
|
|
132
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
133
|
+
const stream = fs.createWriteStream(out);
|
|
134
|
+
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
archive
|
|
137
|
+
.directory(source, false)
|
|
138
|
+
.on('error', err => reject(err))
|
|
139
|
+
.pipe(stream);
|
|
140
|
+
|
|
141
|
+
if (extraFile) {
|
|
142
|
+
archive.file(extraFile.source, { name: extraFile.name });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
stream.on('close', () => resolve());
|
|
146
|
+
archive.finalize();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getSize(filePath: string) {
|
|
151
|
+
const stats = fs.statSync(filePath);
|
|
152
|
+
const bytes = stats.size;
|
|
153
|
+
if (bytes < 1024) return bytes + ' B';
|
|
154
|
+
return (bytes / 1024).toFixed(2) + ' KB';
|
|
155
|
+
}
|