@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
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @klyb/cli
|
|
2
|
+
|
|
3
|
+
The official Command Line Interface for building widgets and pages for the **Klyb Community Builder**.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @klyb/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### 1. Create a new Widget
|
|
14
|
+
|
|
15
|
+
Scaffold a new widget project with the official React + Vite + TypeScript template.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
klyb init my-awesome-widget
|
|
19
|
+
cd my-awesome-widget
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Develop Locally (Simulator)
|
|
24
|
+
|
|
25
|
+
Start the local development server. This launches a **Local Simulator** that mimics the Klyb mobile environment (IFrame, SDK communication, etc.), allowing you to test your widget in isolation.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run dev
|
|
29
|
+
# OR
|
|
30
|
+
klyb dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The simulator will open at `http://localhost:3000` (or the next available port).
|
|
34
|
+
|
|
35
|
+
### 3. Deploy
|
|
36
|
+
|
|
37
|
+
Package your widget and deploy it to the Klyb platform.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm run deploy
|
|
41
|
+
# OR
|
|
42
|
+
klyb deploy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Project Structure
|
|
46
|
+
|
|
47
|
+
A standard Klyb widget project looks like this:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
my-widget/
|
|
51
|
+
├── src/
|
|
52
|
+
│ ├── App.tsx # Your main widget code
|
|
53
|
+
│ └── main.tsx # Entry point
|
|
54
|
+
├── klyb.json # Widget metadata (name, version, permissions)
|
|
55
|
+
├── package.json
|
|
56
|
+
└── vite.config.ts # Vite configuration
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## SDK Integration
|
|
60
|
+
|
|
61
|
+
The CLI templates come pre-configured with `@klyb/sdk`. Use it to interact with the host application:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { bridge } from '@klyb/sdk';
|
|
65
|
+
|
|
66
|
+
// Get user info
|
|
67
|
+
const user = await bridge.getUser();
|
|
68
|
+
|
|
69
|
+
// Show a native toast
|
|
70
|
+
await bridge.showToast({ message: 'Hello from Widget!', type: 'success' });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Contributing
|
|
74
|
+
|
|
75
|
+
This CLI is part of the Klyb monorepo.
|
|
76
|
+
|
|
77
|
+
### Local Setup
|
|
78
|
+
|
|
79
|
+
1. Clone the repository.
|
|
80
|
+
2. Install dependencies: `npm install`
|
|
81
|
+
3. Build the CLI: `npm run build`
|
|
82
|
+
4. Link globally for testing: `npm link`
|
package/bin/klyb.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
exports.deployCommand = deployCommand;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
12
|
+
const axios_1 = __importDefault(require("axios"));
|
|
13
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
14
|
+
const credentials_1 = require("../credentials");
|
|
15
|
+
async function deployCommand() {
|
|
16
|
+
console.log(chalk_1.default.blue('🚀 Deploying Widget to Klyb...'));
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const configPath = path_1.default.join(cwd, 'klyb.json');
|
|
19
|
+
// On vérifie qu'on est bien à la racine d'un projet de widget (présence du klyb.json)
|
|
20
|
+
if (!fs_extra_1.default.existsSync(configPath)) {
|
|
21
|
+
console.error(chalk_1.default.red('❌ klyb.json not found. Are you in a Klyb project?'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const config = await fs_extra_1.default.readJson(configPath);
|
|
25
|
+
console.log(chalk_1.default.dim(` Project: ${config.name} v${config.version}`));
|
|
26
|
+
// On compile le code source du projet (TypeScript, React, etc.)
|
|
27
|
+
console.log(chalk_1.default.yellow('\n📦 Building project...'));
|
|
28
|
+
try {
|
|
29
|
+
(0, child_process_1.execSync)('npm run build', { stdio: 'inherit' });
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.error(chalk_1.default.red('❌ Build failed. Fix errors and try again.'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// On prépare l'archive contenant uniquement les fichiers compilés (le dossier dist)
|
|
36
|
+
console.log(chalk_1.default.yellow('\n🤐 Zipping assets...'));
|
|
37
|
+
const distDir = path_1.default.join(cwd, 'dist');
|
|
38
|
+
const zipPath = path_1.default.join(cwd, 'bundle.zip');
|
|
39
|
+
if (!fs_extra_1.default.existsSync(distDir)) {
|
|
40
|
+
console.error(chalk_1.default.red('❌ dist/ directory not found after build.'));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
await zipDirectory(distDir, zipPath, { source: configPath, name: 'klyb.json' });
|
|
44
|
+
console.log(chalk_1.default.green(` Bundle created: ${getSize(zipPath)}`));
|
|
45
|
+
// Étape finale : on envoie tout ça vers la plateforme Klyb
|
|
46
|
+
console.log(chalk_1.default.yellow('\n☁️ Uploading to Klyb Registry...'));
|
|
47
|
+
// Récupération des accès enregistrés lors du `klyb login`
|
|
48
|
+
const credentials = (0, credentials_1.getCredentials)();
|
|
49
|
+
const apiUrl = credentials?.apiUrl || process.env.KLYB_API_URL || 'http://localhost:3000';
|
|
50
|
+
if (!credentials?.token) {
|
|
51
|
+
console.error(chalk_1.default.red('❌ Not authenticated.'));
|
|
52
|
+
console.error(chalk_1.default.yellow(' Run: ') + chalk_1.default.cyan('klyb login'));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const token = credentials.token;
|
|
56
|
+
try {
|
|
57
|
+
const isSubmission = process.argv.includes('--submit');
|
|
58
|
+
const status = isSubmission ? 'published' : 'draft';
|
|
59
|
+
const form = new form_data_1.default();
|
|
60
|
+
form.append('file', fs_extra_1.default.createReadStream(zipPath));
|
|
61
|
+
form.append('manifest', JSON.stringify(config));
|
|
62
|
+
// Appel direct à l'API pour lancer la publication
|
|
63
|
+
const deployUrl = `${apiUrl}/api/widget-library/developer/deploy`;
|
|
64
|
+
console.log(chalk_1.default.blue(`\n🚀 Deploying to ${deployUrl}...`));
|
|
65
|
+
const response = await axios_1.default.post(deployUrl, form, {
|
|
66
|
+
headers: {
|
|
67
|
+
...form.getHeaders(),
|
|
68
|
+
'Authorization': `Bearer ${token}`
|
|
69
|
+
},
|
|
70
|
+
maxContentLength: Infinity,
|
|
71
|
+
maxBodyLength: Infinity
|
|
72
|
+
});
|
|
73
|
+
console.log(chalk_1.default.green('\n✅ Deployment Successful!'));
|
|
74
|
+
console.log(chalk_1.default.cyan(` Widget ID: ${response.data.widgetId}`));
|
|
75
|
+
console.log(chalk_1.default.cyan(` Version: ${response.data.version}`));
|
|
76
|
+
console.log(chalk_1.default.dim(` Download URL: ${response.data.url}`));
|
|
77
|
+
// On s'assure que le projet local garde bien l'identifiant définitif du widget
|
|
78
|
+
if (config.id !== response.data.widgetId) {
|
|
79
|
+
config.id = response.data.widgetId;
|
|
80
|
+
await fs_extra_1.default.writeJson(configPath, config, { spaces: 2 });
|
|
81
|
+
console.log(chalk_1.default.dim(` (Updated local klyb.json with new ID)`));
|
|
82
|
+
}
|
|
83
|
+
if (isSubmission) {
|
|
84
|
+
console.log(chalk_1.default.magenta(' 🚀 Submitted for validation (Status: Pending)'));
|
|
85
|
+
console.log(chalk_1.default.dim(' An admin will review your widget shortly.'));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log(chalk_1.default.dim(' Saved as Draft. PRO TIP: Use --submit to send for validation.'));
|
|
89
|
+
}
|
|
90
|
+
console.log(chalk_1.default.dim(' Available in your "Submissions" tab.'));
|
|
91
|
+
// On supprime le fichier zip qui ne sert plus à rien
|
|
92
|
+
await fs_extra_1.default.remove(zipPath);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error(chalk_1.default.red('\n❌ Upload failed'));
|
|
96
|
+
if (error.response) {
|
|
97
|
+
console.error(chalk_1.default.red(` Server Error: ${error.response.status} ${error.response.statusText}`));
|
|
98
|
+
console.error(chalk_1.default.yellow(` Response Data: ${JSON.stringify(error.response.data, null, 2)}`));
|
|
99
|
+
}
|
|
100
|
+
else if (error.request) {
|
|
101
|
+
console.error(chalk_1.default.red(` No response from server`));
|
|
102
|
+
console.error(chalk_1.default.yellow(` API URL: ${apiUrl}`));
|
|
103
|
+
console.error(chalk_1.default.dim(` Check if the API is running on ${apiUrl}`));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.error(chalk_1.default.red(` Error: ${error.message}`));
|
|
107
|
+
}
|
|
108
|
+
// On n'oublie pas de supprimer le fichier zip même si l'envoi a échoué
|
|
109
|
+
try {
|
|
110
|
+
if (await fs_extra_1.default.pathExists(zipPath)) {
|
|
111
|
+
await fs_extra_1.default.remove(zipPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (cleanupError) {
|
|
115
|
+
// Ignore cleanup errors
|
|
116
|
+
}
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function zipDirectory(source, out, extraFile) {
|
|
121
|
+
const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
|
|
122
|
+
const stream = fs_extra_1.default.createWriteStream(out);
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
archive
|
|
125
|
+
.directory(source, false)
|
|
126
|
+
.on('error', err => reject(err))
|
|
127
|
+
.pipe(stream);
|
|
128
|
+
if (extraFile) {
|
|
129
|
+
archive.file(extraFile.source, { name: extraFile.name });
|
|
130
|
+
}
|
|
131
|
+
stream.on('close', () => resolve());
|
|
132
|
+
archive.finalize();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function getSize(filePath) {
|
|
136
|
+
const stats = fs_extra_1.default.statSync(filePath);
|
|
137
|
+
const bytes = stats.size;
|
|
138
|
+
if (bytes < 1024)
|
|
139
|
+
return bytes + ' B';
|
|
140
|
+
return (bytes / 1024).toFixed(2) + ' KB';
|
|
141
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
exports.devCommand = devCommand;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const express_1 = __importDefault(require("express"));
|
|
9
|
+
const cross_spawn_1 = __importDefault(require("cross-spawn"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
13
|
+
const axios_1 = __importDefault(require("axios"));
|
|
14
|
+
async function devCommand() {
|
|
15
|
+
console.log(chalk_1.default.blue('🚀 Starting Klyb Development Environment...'));
|
|
16
|
+
// 0. Load Environment Variables from the User's Project
|
|
17
|
+
const userEnvPath = path_1.default.join(process.cwd(), '.env');
|
|
18
|
+
if (fs_1.default.existsSync(userEnvPath)) {
|
|
19
|
+
dotenv_1.default.config({ path: userEnvPath });
|
|
20
|
+
console.log(chalk_1.default.green('✅ Loaded .env file'));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(chalk_1.default.yellow('⚠️ No .env file found. API Key integration will be disabled.'));
|
|
24
|
+
}
|
|
25
|
+
// 1. Start User's Widget Server (Vite)
|
|
26
|
+
const widgetPort = 3001;
|
|
27
|
+
console.log(chalk_1.default.dim(` Starting widget server on port ${widgetPort}...`));
|
|
28
|
+
const widgetProcess = (0, cross_spawn_1.default)('npm', ['run', 'dev', '--', '--port', widgetPort.toString()], {
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
env: { ...process.env, FORCE_COLOR: 'true' }
|
|
31
|
+
});
|
|
32
|
+
widgetProcess.on('error', (err) => {
|
|
33
|
+
console.error(chalk_1.default.red('❌ Failed to start widget server'), err);
|
|
34
|
+
});
|
|
35
|
+
// 2. Start Simulator Server
|
|
36
|
+
const app = (0, express_1.default)();
|
|
37
|
+
const simulatorPort = 3002; // Changed from 3000 to avoid conflict with API
|
|
38
|
+
const realApiUrl = process.env.KLYB_API_URL || 'http://localhost:3000';
|
|
39
|
+
// API Route for Simulator to get Configuration
|
|
40
|
+
app.get('/simulator/api/config', async (req, res) => {
|
|
41
|
+
const apiKey = process.env.KLYB_API_KEY;
|
|
42
|
+
let user = null;
|
|
43
|
+
if (apiKey) {
|
|
44
|
+
try {
|
|
45
|
+
// Determine if apiKey is a JWT or a special key.
|
|
46
|
+
// For now, assuming it's a Bearer Token (Personal Access Token)
|
|
47
|
+
const response = await axios_1.default.get(`${realApiUrl}/api/auth/me`, {
|
|
48
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
49
|
+
});
|
|
50
|
+
user = response.data;
|
|
51
|
+
// Add role/permissions if needed
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(chalk_1.default.red('❌ Failed to validate API Key:'), error.message);
|
|
55
|
+
// Don't fail the request, just return guest mode with error
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
res.json({
|
|
59
|
+
user: user || { id: 'guest', name: 'Guest Developer', role: 'guest' },
|
|
60
|
+
mode: apiKey ? 'authenticated' : 'guest',
|
|
61
|
+
apiUrl: realApiUrl
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// Resolve path to built simulator client assets
|
|
65
|
+
let simulatorDist = path_1.default.join(__dirname, '../../simulator/client');
|
|
66
|
+
if (!fs_1.default.existsSync(simulatorDist) || !fs_1.default.existsSync(path_1.default.join(simulatorDist, 'index.html'))) {
|
|
67
|
+
console.warn(chalk_1.default.yellow('⚠️ Simulator build not found. Ensure CLI is built.'));
|
|
68
|
+
simulatorDist = path_1.default.join(__dirname, '../../../dist/simulator/client');
|
|
69
|
+
}
|
|
70
|
+
app.use(express_1.default.static(simulatorDist));
|
|
71
|
+
app.get('*', (req, res) => {
|
|
72
|
+
// handle api routes not found
|
|
73
|
+
if (req.path.startsWith('/simulator/api')) {
|
|
74
|
+
return res.status(404).json({ error: 'Not Found' });
|
|
75
|
+
}
|
|
76
|
+
res.sendFile(path_1.default.join(simulatorDist, 'index.html'));
|
|
77
|
+
});
|
|
78
|
+
app.listen(simulatorPort, () => {
|
|
79
|
+
console.log(chalk_1.default.green(`\n📱 Simulator running at http://localhost:${simulatorPort}`));
|
|
80
|
+
console.log(chalk_1.default.cyan(` Widget running at http://localhost:${widgetPort}`));
|
|
81
|
+
console.log(chalk_1.default.dim(' Press Ctrl+C to stop.'));
|
|
82
|
+
});
|
|
83
|
+
process.on('SIGINT', () => {
|
|
84
|
+
widgetProcess.kill();
|
|
85
|
+
process.exit();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
exports.initCommand = initCommand;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
// In CommonJS, __dirname is available globally
|
|
13
|
+
async function initCommand(name) {
|
|
14
|
+
const targetDir = path_1.default.resolve(process.cwd(), name);
|
|
15
|
+
if (fs_extra_1.default.existsSync(targetDir)) {
|
|
16
|
+
console.error(chalk_1.default.red(`❌ Directory ${name} already exists.`));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
console.log(chalk_1.default.blue(`🚀 Creating new Klyb widget project: ${name}`));
|
|
20
|
+
// 1. Copy Template
|
|
21
|
+
// In dev (ts-node/src), template is in src/templates
|
|
22
|
+
// In prod (dist), template is in dist/templates (need to ensure build copies it)
|
|
23
|
+
// For now we assume we are running from src or built dist where structure is preserved.
|
|
24
|
+
// Adjust path based on where we are executing.
|
|
25
|
+
// We need to resolve the template path relative to this file.
|
|
26
|
+
// If we are in dist/commands/init.js, template should be in ../templates/react-vite-ts relative to this file?
|
|
27
|
+
// No, standard is usually to keep templates as assets.
|
|
28
|
+
// Let's assume we are in src/commands/init.ts for dev flow or dist/commands/init.js
|
|
29
|
+
// Quick fix for path resolution in both envs: look for templates dir up the tree
|
|
30
|
+
const templateName = 'react-vite-ts';
|
|
31
|
+
let templateDir = path_1.default.join(__dirname, '../../templates', templateName); // from dist/commands or src/commands
|
|
32
|
+
// Fallback if structure is different (e.g. src vs dist) - explicit check
|
|
33
|
+
if (!fs_extra_1.default.existsSync(templateDir)) {
|
|
34
|
+
// Try source path if we are running from dist but templates are in src (common in local dev)
|
|
35
|
+
templateDir = path_1.default.join(__dirname, '../../src/templates', templateName);
|
|
36
|
+
}
|
|
37
|
+
if (!fs_extra_1.default.existsSync(templateDir)) {
|
|
38
|
+
console.error(chalk_1.default.red(`❌ Template not found at ${templateDir}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await fs_extra_1.default.copy(templateDir, targetDir);
|
|
43
|
+
// 2. Customize package.json
|
|
44
|
+
const pkgPath = path_1.default.join(targetDir, 'package.json');
|
|
45
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
46
|
+
pkg.name = name;
|
|
47
|
+
await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
48
|
+
// 3. Rename files starting with _ (to avoid npm stripping them)
|
|
49
|
+
const gitignorePath = path_1.default.join(targetDir, '_gitignore');
|
|
50
|
+
if (fs_extra_1.default.existsSync(gitignorePath)) {
|
|
51
|
+
await fs_extra_1.default.move(gitignorePath, path_1.default.join(targetDir, '.gitignore'));
|
|
52
|
+
}
|
|
53
|
+
const envExamplePath = path_1.default.join(targetDir, '_env.example');
|
|
54
|
+
if (fs_extra_1.default.existsSync(envExamplePath)) {
|
|
55
|
+
await fs_extra_1.default.move(envExamplePath, path_1.default.join(targetDir, '.env.example'));
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk_1.default.green(`✅ Project created in ${targetDir}`));
|
|
58
|
+
// 4. Prompt for install
|
|
59
|
+
const { install } = await inquirer_1.default.prompt([{
|
|
60
|
+
type: 'confirm',
|
|
61
|
+
name: 'install',
|
|
62
|
+
message: 'Install dependencies now?',
|
|
63
|
+
default: true
|
|
64
|
+
}]);
|
|
65
|
+
if (install) {
|
|
66
|
+
console.log(chalk_1.default.yellow('📦 Installing dependencies...'));
|
|
67
|
+
(0, child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
|
|
68
|
+
console.log(chalk_1.default.green('✅ Dependencies installed'));
|
|
69
|
+
}
|
|
70
|
+
console.log('\nNext steps:');
|
|
71
|
+
console.log(chalk_1.default.cyan(` cd ${name}`));
|
|
72
|
+
console.log(chalk_1.default.cyan(' npm run dev'));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
console.error(chalk_1.default.red('❌ Failed to create project'), e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.loginCommand = loginCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const http_1 = __importDefault(require("http"));
|
|
42
|
+
const credentials_1 = require("../credentials");
|
|
43
|
+
const DEFAULT_API_URL = process.env.KLYB_API_URL || 'http://localhost:3000';
|
|
44
|
+
/**
|
|
45
|
+
* `klyb login`
|
|
46
|
+
*
|
|
47
|
+
* 1. Starts a one-shot local HTTP server on a random port
|
|
48
|
+
* 2. Opens the browser to the backend's CLI login page
|
|
49
|
+
* 3. After the user logs in, the backend redirects back with the JWT
|
|
50
|
+
* 4. CLI saves the token to ~/.klyb/credentials.json
|
|
51
|
+
*/
|
|
52
|
+
async function loginCommand() {
|
|
53
|
+
const apiUrl = DEFAULT_API_URL;
|
|
54
|
+
console.log(chalk_1.default.blue('🔑 Logging in to Klyb...'));
|
|
55
|
+
console.log(chalk_1.default.dim(` API: ${apiUrl}\n`));
|
|
56
|
+
const token = await waitForToken(apiUrl);
|
|
57
|
+
// Persist credentials
|
|
58
|
+
(0, credentials_1.saveCredentials)({
|
|
59
|
+
token,
|
|
60
|
+
apiUrl,
|
|
61
|
+
savedAt: new Date().toISOString(),
|
|
62
|
+
});
|
|
63
|
+
console.log(chalk_1.default.green('\n✅ Logged in successfully!'));
|
|
64
|
+
console.log(chalk_1.default.dim(` Credentials saved to: ${(0, credentials_1.credentialsPath)()}`));
|
|
65
|
+
console.log(chalk_1.default.dim(' You can now run: klyb deploy'));
|
|
66
|
+
}
|
|
67
|
+
function waitForToken(apiUrl) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
// One-shot HTTP server to receive the callback
|
|
70
|
+
const server = http_1.default.createServer((req, res) => {
|
|
71
|
+
const url = new URL(req.url, `http://localhost`);
|
|
72
|
+
if (url.pathname === '/callback') {
|
|
73
|
+
const token = url.searchParams.get('token');
|
|
74
|
+
const error = url.searchParams.get('error');
|
|
75
|
+
if (token) {
|
|
76
|
+
// Success — show a nice page to the user
|
|
77
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
78
|
+
res.end(`
|
|
79
|
+
<!DOCTYPE html>
|
|
80
|
+
<html>
|
|
81
|
+
<head><title>Klyb CLI</title></head>
|
|
82
|
+
<body style="font-family:sans-serif;text-align:center;padding:4rem;background:#0f172a;color:white">
|
|
83
|
+
<h1 style="color:#22d3ee">✅ Logged in!</h1>
|
|
84
|
+
<p style="color:#94a3b8">You can close this window and return to your terminal.</p>
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|
|
87
|
+
`);
|
|
88
|
+
server.close();
|
|
89
|
+
resolve(token);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
93
|
+
res.end(`<p>Error: ${error || 'unknown'}</p>`);
|
|
94
|
+
server.close();
|
|
95
|
+
reject(new Error(error || 'Login failed'));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
res.writeHead(404);
|
|
100
|
+
res.end();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// Listen on a random available port
|
|
104
|
+
server.listen(0, '127.0.0.1', () => {
|
|
105
|
+
const address = server.address();
|
|
106
|
+
const callbackUrl = `http://127.0.0.1:${address.port}/callback`;
|
|
107
|
+
const loginUrl = `${apiUrl}/api/auth/cli-login?redirect=${encodeURIComponent(callbackUrl)}`;
|
|
108
|
+
console.log(chalk_1.default.cyan(' Opening browser for authentication...'));
|
|
109
|
+
console.log(chalk_1.default.dim(` If the browser doesn't open, visit:\n ${loginUrl}\n`));
|
|
110
|
+
// Dynamic import to handle ESM-only open package
|
|
111
|
+
Promise.resolve().then(() => __importStar(require('open'))).then(({ default: openBrowser }) => openBrowser(loginUrl)).catch(() => { });
|
|
112
|
+
});
|
|
113
|
+
// Timeout after 5 minutes
|
|
114
|
+
const timeout = setTimeout(() => {
|
|
115
|
+
server.close();
|
|
116
|
+
reject(new Error('Login timed out. Please try again.'));
|
|
117
|
+
}, 5 * 60 * 1000);
|
|
118
|
+
server.on('close', () => clearTimeout(timeout));
|
|
119
|
+
server.on('error', reject);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
exports.getCredentials = getCredentials;
|
|
7
|
+
exports.saveCredentials = saveCredentials;
|
|
8
|
+
exports.clearCredentials = clearCredentials;
|
|
9
|
+
exports.credentialsPath = credentialsPath;
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
|
+
const CREDENTIALS_PATH = path_1.default.join(os_1.default.homedir(), '.klyb', 'credentials.json');
|
|
14
|
+
function getCredentials() {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs_extra_1.default.existsSync(CREDENTIALS_PATH))
|
|
17
|
+
return null;
|
|
18
|
+
return fs_extra_1.default.readJsonSync(CREDENTIALS_PATH);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function saveCredentials(creds) {
|
|
25
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(CREDENTIALS_PATH));
|
|
26
|
+
fs_extra_1.default.writeJsonSync(CREDENTIALS_PATH, creds, { spaces: 2 });
|
|
27
|
+
}
|
|
28
|
+
function clearCredentials() {
|
|
29
|
+
if (fs_extra_1.default.existsSync(CREDENTIALS_PATH)) {
|
|
30
|
+
fs_extra_1.default.removeSync(CREDENTIALS_PATH);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function credentialsPath() {
|
|
34
|
+
return CREDENTIALS_PATH;
|
|
35
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commander_1 = require("commander");
|
|
4
|
+
const init_1 = require("./commands/init");
|
|
5
|
+
const dev_1 = require("./commands/dev");
|
|
6
|
+
const deploy_1 = require("./commands/deploy");
|
|
7
|
+
const login_1 = require("./commands/login");
|
|
8
|
+
const program = new commander_1.Command();
|
|
9
|
+
program
|
|
10
|
+
.name('klyb')
|
|
11
|
+
.description('Klyb Developer CLI')
|
|
12
|
+
.version('0.1.0');
|
|
13
|
+
program
|
|
14
|
+
.command('login')
|
|
15
|
+
.description('Authenticate with Klyb (opens browser)')
|
|
16
|
+
.action(login_1.loginCommand);
|
|
17
|
+
program
|
|
18
|
+
.command('init')
|
|
19
|
+
.description('Initialize a new Klyb widget project')
|
|
20
|
+
.argument('<name>', 'Project name')
|
|
21
|
+
.action(init_1.initCommand);
|
|
22
|
+
program
|
|
23
|
+
.command('dev')
|
|
24
|
+
.description('Start development server with Simulator')
|
|
25
|
+
.action(dev_1.devCommand);
|
|
26
|
+
program
|
|
27
|
+
.command('deploy')
|
|
28
|
+
.description('Build and deploy widget to Klyb')
|
|
29
|
+
.option('--submit', 'Submit for validation immediately')
|
|
30
|
+
.action(deploy_1.deployCommand);
|
|
31
|
+
program.parse();
|