@zaflun/lumio-cli 0.1.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/LICENSE.md +21 -0
- package/README.md +77 -0
- package/bin/lumio.js +2 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +800 -0
- package/package.json +61 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zaflun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @zaflun/lumio-cli
|
|
2
|
+
|
|
3
|
+
The official CLI for building, testing, and deploying [Lumio](https://lumio.vision) extensions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @zaflun/lumio-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Log in to Lumio
|
|
15
|
+
lumio login
|
|
16
|
+
|
|
17
|
+
# Create a new extension
|
|
18
|
+
lumio init my-widget
|
|
19
|
+
|
|
20
|
+
# Start development
|
|
21
|
+
cd my-widget
|
|
22
|
+
npm install
|
|
23
|
+
lumio dev
|
|
24
|
+
|
|
25
|
+
# Build and deploy
|
|
26
|
+
lumio build
|
|
27
|
+
lumio deploy -v 1.0.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
| Command | Description |
|
|
33
|
+
|---------|-------------|
|
|
34
|
+
| `lumio login` | Authenticate via browser (PKCE) or token (`--token`) |
|
|
35
|
+
| `lumio logout` | Remove local credentials |
|
|
36
|
+
| `lumio whoami` | Show current user and account |
|
|
37
|
+
| `lumio init [name]` | Create a new extension project (interactive wizard) |
|
|
38
|
+
| `lumio dev` | Start local dev server with hot-reload |
|
|
39
|
+
| `lumio build` | Bundle extension for deployment |
|
|
40
|
+
| `lumio deploy` | Upload extension version (flags: `-v`, `-d`, `--breaking`, `--draft`) |
|
|
41
|
+
| `lumio env set <KEY> <VALUE>` | Set a server-side secret |
|
|
42
|
+
| `lumio env list` | List secret keys |
|
|
43
|
+
| `lumio env delete <KEY>` | Delete a secret |
|
|
44
|
+
| `lumio run <type:name>` | Execute a server function locally |
|
|
45
|
+
| `lumio status` | Show review status of latest version |
|
|
46
|
+
| `lumio logs` | Stream server function logs |
|
|
47
|
+
| `lumio dashboard` | Open extension dashboard in browser |
|
|
48
|
+
|
|
49
|
+
## CI/CD
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Token-based login for CI
|
|
53
|
+
lumio login --token $LUMIO_API_TOKEN
|
|
54
|
+
|
|
55
|
+
# Deploy in CI
|
|
56
|
+
lumio deploy -v $VERSION -d "Automated release"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Team Support
|
|
60
|
+
|
|
61
|
+
When you have developer teams, `lumio init` asks who owns the extension:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
? Who owns this extension?
|
|
65
|
+
❯ Personal (Solo Developer)
|
|
66
|
+
Team: Zaflun GmbH
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
- [CLI Reference](https://developers.lumio.vision/cli/install)
|
|
72
|
+
- [Publishing Guide](https://developers.lumio.vision/publishing/submission-wizard)
|
|
73
|
+
- [Full Docs](https://developers.lumio.vision)
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT © [zaflun](https://withzaflun.com)
|
package/bin/lumio.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
declare function createProgram(): Command;
|
|
4
|
+
|
|
5
|
+
declare class CredentialStore {
|
|
6
|
+
readonly credentialsPath: string;
|
|
7
|
+
constructor();
|
|
8
|
+
getToken(): string | null;
|
|
9
|
+
getApiUrl(): string;
|
|
10
|
+
isAuthenticated(): boolean;
|
|
11
|
+
saveToken(token: string, apiUrl?: string): void;
|
|
12
|
+
clear(): void;
|
|
13
|
+
private readCredentials;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class LumioApiClient {
|
|
17
|
+
readonly baseUrl: string;
|
|
18
|
+
private readonly token;
|
|
19
|
+
constructor(baseUrl: string, token: string);
|
|
20
|
+
url(endpoint: string): string;
|
|
21
|
+
authHeaders(): Record<string, string>;
|
|
22
|
+
uploadHeaders(): Record<string, string>;
|
|
23
|
+
get<T>(endpoint: string): Promise<T>;
|
|
24
|
+
post<T>(endpoint: string, body?: unknown): Promise<T>;
|
|
25
|
+
put<T>(endpoint: string, body?: unknown): Promise<T>;
|
|
26
|
+
delete<T>(endpoint: string): Promise<T>;
|
|
27
|
+
uploadFile(endpoint: string, formData: FormData): Promise<unknown>;
|
|
28
|
+
static isCI(): boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { CredentialStore, LumioApiClient, createProgram };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
// src/program.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
// src/auth/credential-store.ts
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
var DEFAULT_API_URL = "https://api.lumio.vision";
|
|
9
|
+
var CredentialStore = class {
|
|
10
|
+
credentialsPath;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.credentialsPath = path.join(os.homedir(), ".lumio", "credentials");
|
|
13
|
+
}
|
|
14
|
+
getToken() {
|
|
15
|
+
const creds = this.readCredentials();
|
|
16
|
+
return creds?.token ?? null;
|
|
17
|
+
}
|
|
18
|
+
getApiUrl() {
|
|
19
|
+
const creds = this.readCredentials();
|
|
20
|
+
return creds?.api_url ?? DEFAULT_API_URL;
|
|
21
|
+
}
|
|
22
|
+
isAuthenticated() {
|
|
23
|
+
return this.getToken() !== null;
|
|
24
|
+
}
|
|
25
|
+
saveToken(token, apiUrl) {
|
|
26
|
+
const dir = path.dirname(this.credentialsPath);
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
const credentials = {
|
|
29
|
+
token,
|
|
30
|
+
api_url: apiUrl ?? DEFAULT_API_URL
|
|
31
|
+
};
|
|
32
|
+
fs.writeFileSync(
|
|
33
|
+
this.credentialsPath,
|
|
34
|
+
JSON.stringify(credentials, null, 2),
|
|
35
|
+
{ mode: 384 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
clear() {
|
|
39
|
+
if (fs.existsSync(this.credentialsPath)) {
|
|
40
|
+
fs.unlinkSync(this.credentialsPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
readCredentials() {
|
|
44
|
+
if (!fs.existsSync(this.credentialsPath)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(this.credentialsPath, "utf-8");
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/commands/login.ts
|
|
57
|
+
function registerLogin(program2) {
|
|
58
|
+
program2.command("login").description("Authenticate with Lumio via browser (PKCE) or token").option("--token <token>", "Authenticate with an API token (for CI/CD)").option("--api-url <url>", "Custom API URL", "https://api.lumio.vision").action(async (options) => {
|
|
59
|
+
const store = new CredentialStore();
|
|
60
|
+
if (options.token) {
|
|
61
|
+
store.saveToken(options.token, options.apiUrl);
|
|
62
|
+
console.log("Authenticated with token.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log("Opening browser for authentication...");
|
|
66
|
+
const { default: open } = await import("open");
|
|
67
|
+
const pkceUrl = `${options.apiUrl}/v1/auth/cli/authorize`;
|
|
68
|
+
await open(pkceUrl);
|
|
69
|
+
console.log("Waiting for authentication callback...");
|
|
70
|
+
console.log("If the browser did not open, visit:", pkceUrl);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/commands/logout.ts
|
|
75
|
+
function registerLogout(program2) {
|
|
76
|
+
program2.command("logout").description("Remove local auth credentials").action(() => {
|
|
77
|
+
const store = new CredentialStore();
|
|
78
|
+
if (!store.isAuthenticated()) {
|
|
79
|
+
console.log("Not logged in.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
store.clear();
|
|
83
|
+
console.log("Logged out. Credentials removed.");
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/api/client.ts
|
|
88
|
+
var LumioApiClient = class {
|
|
89
|
+
baseUrl;
|
|
90
|
+
token;
|
|
91
|
+
constructor(baseUrl, token) {
|
|
92
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
93
|
+
this.token = token;
|
|
94
|
+
}
|
|
95
|
+
url(endpoint) {
|
|
96
|
+
return `${this.baseUrl}${endpoint}`;
|
|
97
|
+
}
|
|
98
|
+
authHeaders() {
|
|
99
|
+
return {
|
|
100
|
+
"Authorization": `Bearer ${this.token}`,
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
uploadHeaders() {
|
|
105
|
+
return {
|
|
106
|
+
"Authorization": `Bearer ${this.token}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async get(endpoint) {
|
|
110
|
+
const response = await fetch(this.url(endpoint), {
|
|
111
|
+
method: "GET",
|
|
112
|
+
headers: this.authHeaders()
|
|
113
|
+
});
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
throw new Error(`API error ${response.status}: ${await response.text()}`);
|
|
116
|
+
}
|
|
117
|
+
return response.json();
|
|
118
|
+
}
|
|
119
|
+
async post(endpoint, body) {
|
|
120
|
+
const response = await fetch(this.url(endpoint), {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: this.authHeaders(),
|
|
123
|
+
body: body ? JSON.stringify(body) : void 0
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new Error(`API error ${response.status}: ${await response.text()}`);
|
|
127
|
+
}
|
|
128
|
+
return response.json();
|
|
129
|
+
}
|
|
130
|
+
async put(endpoint, body) {
|
|
131
|
+
const response = await fetch(this.url(endpoint), {
|
|
132
|
+
method: "PUT",
|
|
133
|
+
headers: this.authHeaders(),
|
|
134
|
+
body: body ? JSON.stringify(body) : void 0
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw new Error(`API error ${response.status}: ${await response.text()}`);
|
|
138
|
+
}
|
|
139
|
+
return response.json();
|
|
140
|
+
}
|
|
141
|
+
async delete(endpoint) {
|
|
142
|
+
const response = await fetch(this.url(endpoint), {
|
|
143
|
+
method: "DELETE",
|
|
144
|
+
headers: this.authHeaders()
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error(`API error ${response.status}: ${await response.text()}`);
|
|
148
|
+
}
|
|
149
|
+
return response.json();
|
|
150
|
+
}
|
|
151
|
+
async uploadFile(endpoint, formData) {
|
|
152
|
+
const response = await fetch(this.url(endpoint), {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: this.uploadHeaders(),
|
|
155
|
+
body: formData
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error(`Upload error ${response.status}: ${await response.text()}`);
|
|
159
|
+
}
|
|
160
|
+
return response.json();
|
|
161
|
+
}
|
|
162
|
+
static isCI() {
|
|
163
|
+
return process.env.CI === "true" || process.env.CI === "1" || !!process.env.GITHUB_ACTIONS;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/commands/whoami.ts
|
|
168
|
+
function registerWhoami(program2) {
|
|
169
|
+
program2.command("whoami").description("Show current authenticated user and account").action(async () => {
|
|
170
|
+
const store = new CredentialStore();
|
|
171
|
+
const token = store.getToken();
|
|
172
|
+
if (!token) {
|
|
173
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
177
|
+
try {
|
|
178
|
+
const me = await client.get("/v1/users/me");
|
|
179
|
+
console.log(`User: ${me.email}`);
|
|
180
|
+
console.log(`Account: ${me.account_name}`);
|
|
181
|
+
console.log(`API: ${store.getApiUrl()}`);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error("Failed to fetch user info:", err);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/commands/init.ts
|
|
190
|
+
import * as fs2 from "fs";
|
|
191
|
+
import * as path2 from "path";
|
|
192
|
+
function registerInit(program2) {
|
|
193
|
+
program2.command("init").argument("[name]", "Extension project name").description("Create a new extension project with interactive wizard").action(async (name) => {
|
|
194
|
+
const store = new CredentialStore();
|
|
195
|
+
const token = store.getToken();
|
|
196
|
+
if (!token) {
|
|
197
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
const { default: inquirer } = await import("inquirer");
|
|
201
|
+
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
202
|
+
let ownerType = "developer";
|
|
203
|
+
let teamId = null;
|
|
204
|
+
try {
|
|
205
|
+
const teams = await client.get("/v1/developer/teams");
|
|
206
|
+
if (teams.data.length > 0) {
|
|
207
|
+
const ownerChoice = await inquirer.prompt([
|
|
208
|
+
{
|
|
209
|
+
type: "list",
|
|
210
|
+
name: "owner",
|
|
211
|
+
message: "Who owns this extension?",
|
|
212
|
+
choices: [
|
|
213
|
+
{ name: "Personal (Solo Developer)", value: "developer" },
|
|
214
|
+
...teams.data.map((t) => ({ name: `Team: ${t.name}`, value: `team:${t.id}` }))
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
]);
|
|
218
|
+
if (ownerChoice.owner.startsWith("team:")) {
|
|
219
|
+
ownerType = "team";
|
|
220
|
+
teamId = ownerChoice.owner.replace("team:", "");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
const answers = await inquirer.prompt([
|
|
226
|
+
{ type: "input", name: "name", message: "Extension name:", default: name },
|
|
227
|
+
{ type: "input", name: "description", message: "Description:" },
|
|
228
|
+
{
|
|
229
|
+
type: "list",
|
|
230
|
+
name: "category",
|
|
231
|
+
message: "Category:",
|
|
232
|
+
choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme"]
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
type: "checkbox",
|
|
236
|
+
name: "targets",
|
|
237
|
+
message: "Targets:",
|
|
238
|
+
choices: ["editor", "layer", "interactive"],
|
|
239
|
+
default: ["editor", "layer"]
|
|
240
|
+
},
|
|
241
|
+
{ type: "confirm", name: "server", message: "Server functions?", default: false }
|
|
242
|
+
]);
|
|
243
|
+
const createBody = {
|
|
244
|
+
name: answers.name,
|
|
245
|
+
description: answers.description,
|
|
246
|
+
category: answers.category
|
|
247
|
+
};
|
|
248
|
+
if (ownerType === "team" && teamId) {
|
|
249
|
+
createBody.team_id = teamId;
|
|
250
|
+
}
|
|
251
|
+
const extension = await client.post("/v1/developer/extensions", createBody);
|
|
252
|
+
console.log(`Extension ID: ${extension.id}`);
|
|
253
|
+
const projectDir = path2.resolve(answers.name.toLowerCase().replace(/\s+/g, "-"));
|
|
254
|
+
fs2.mkdirSync(projectDir, { recursive: true });
|
|
255
|
+
const config = {
|
|
256
|
+
$schema: "https://docs.lumio.vision/schemas/lumio.config.schema.json",
|
|
257
|
+
extensionId: extension.id,
|
|
258
|
+
name: answers.name,
|
|
259
|
+
description: answers.description,
|
|
260
|
+
category: answers.category,
|
|
261
|
+
version: "0.1.0",
|
|
262
|
+
targets: answers.targets,
|
|
263
|
+
server: answers.server,
|
|
264
|
+
permissions: [],
|
|
265
|
+
egress: { allowHosts: [], allowPorts: [443] },
|
|
266
|
+
visibility: "private",
|
|
267
|
+
pricing: { type: "free" }
|
|
268
|
+
};
|
|
269
|
+
fs2.writeFileSync(
|
|
270
|
+
path2.join(projectDir, "lumio.config.json"),
|
|
271
|
+
JSON.stringify(config, null, 2)
|
|
272
|
+
);
|
|
273
|
+
const pkg = {
|
|
274
|
+
name: answers.name.toLowerCase().replace(/\s+/g, "-"),
|
|
275
|
+
version: "0.1.0",
|
|
276
|
+
private: true,
|
|
277
|
+
type: "module",
|
|
278
|
+
dependencies: {
|
|
279
|
+
"@zaflun/lumio-sdk": "latest",
|
|
280
|
+
"react": "^19.1.0",
|
|
281
|
+
"react-dom": "^19.1.0"
|
|
282
|
+
},
|
|
283
|
+
devDependencies: {
|
|
284
|
+
"typescript": "^5.8.3",
|
|
285
|
+
"@types/react": "^19.1.0"
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
fs2.writeFileSync(
|
|
289
|
+
path2.join(projectDir, "package.json"),
|
|
290
|
+
JSON.stringify(pkg, null, 2)
|
|
291
|
+
);
|
|
292
|
+
const changelog = `# Changelog
|
|
293
|
+
|
|
294
|
+
## [0.1.0] - ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
295
|
+
|
|
296
|
+
### Added
|
|
297
|
+
- Initial release
|
|
298
|
+
`;
|
|
299
|
+
fs2.writeFileSync(path2.join(projectDir, "CHANGELOG.md"), changelog);
|
|
300
|
+
fs2.mkdirSync(path2.join(projectDir, "src"), { recursive: true });
|
|
301
|
+
for (const target of answers.targets) {
|
|
302
|
+
const content = `import { Lumio, CompactView, Text } from "@zaflun/lumio-sdk";
|
|
303
|
+
|
|
304
|
+
function App() {
|
|
305
|
+
return (
|
|
306
|
+
<CompactView title="${answers.name}">
|
|
307
|
+
<Text content="Hello from ${target}!" />
|
|
308
|
+
</CompactView>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
Lumio.render(<App />, { target: "${target}" });
|
|
313
|
+
`;
|
|
314
|
+
fs2.writeFileSync(path2.join(projectDir, "src", `${target}.tsx`), content);
|
|
315
|
+
}
|
|
316
|
+
if (answers.server) {
|
|
317
|
+
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
318
|
+
const schema = 'import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";\n\nexport default defineSchema({\n items: defineTable({\n text: v.string(),\n completed: v.boolean(),\n }).storage("instance"),\n});\n';
|
|
319
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "schema.ts"), schema);
|
|
320
|
+
const functions = 'import { queryRows, insertRow, patchRow, deleteRow, arg } from "@zaflun/lumio-sdk/server";\n\nexport const getItems = queryRows("items");\nexport const addItem = insertRow("items", { text: arg("text"), completed: false }, { editorOnly: true });\nexport const toggleItem = patchRow("items", arg("id"), { completed: arg("completed") }, { editorOnly: true });\nexport const removeItem = deleteRow("items", arg("id"), { editorOnly: true });\n';
|
|
321
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), functions);
|
|
322
|
+
}
|
|
323
|
+
console.log(`
|
|
324
|
+
Done! Created ${projectDir}/`);
|
|
325
|
+
console.log("Run `lumio dev` to start developing.");
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/commands/dev.ts
|
|
330
|
+
function registerDev(program2) {
|
|
331
|
+
program2.command("dev").description("Start local dev server with hot-reload and mock events").option("-p, --port <port>", "Dev server port", "3100").action(async (options) => {
|
|
332
|
+
console.log(`Starting Lumio dev server on port ${options.port}...`);
|
|
333
|
+
console.log("Mock events enabled. Hot-reload active.");
|
|
334
|
+
console.log("Open http://localhost:" + options.port + " in your browser.");
|
|
335
|
+
console.log("\nDev server implementation requires @zaflun/lumio-sdk and Vite.");
|
|
336
|
+
console.log("Full implementation coming in Phase 12 (Live-Code-Editor).");
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/commands/build.ts
|
|
341
|
+
import * as fs3 from "fs";
|
|
342
|
+
import * as path3 from "path";
|
|
343
|
+
|
|
344
|
+
// src/config/parser.ts
|
|
345
|
+
var ConfigError = class extends Error {
|
|
346
|
+
constructor(message) {
|
|
347
|
+
super(message);
|
|
348
|
+
this.name = "ConfigError";
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
function parseConfigFile(jsonString) {
|
|
352
|
+
const raw = JSON.parse(jsonString);
|
|
353
|
+
return {
|
|
354
|
+
extensionId: raw.extensionId ?? "",
|
|
355
|
+
name: raw.name ?? "",
|
|
356
|
+
description: raw.description,
|
|
357
|
+
category: raw.category ?? "widget",
|
|
358
|
+
version: raw.version ?? "0.1.0",
|
|
359
|
+
targets: raw.targets ?? ["editor"],
|
|
360
|
+
server: raw.server ?? false,
|
|
361
|
+
permissions: raw.permissions ?? [],
|
|
362
|
+
egress: {
|
|
363
|
+
allowHosts: raw.egress?.allowHosts ?? [],
|
|
364
|
+
allowPorts: raw.egress?.allowPorts ?? []
|
|
365
|
+
},
|
|
366
|
+
fonts: raw.fonts,
|
|
367
|
+
visibility: raw.visibility ?? "private",
|
|
368
|
+
pricing: raw.pricing ?? { type: "free" }
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function validateConfig(config) {
|
|
372
|
+
if (!config.extensionId) {
|
|
373
|
+
throw new ConfigError("extensionId is required. Run `lumio init` or create an extension at the dashboard.");
|
|
374
|
+
}
|
|
375
|
+
if (!config.name) {
|
|
376
|
+
throw new ConfigError("name is required in lumio.config.json");
|
|
377
|
+
}
|
|
378
|
+
if (!config.targets || config.targets.length === 0) {
|
|
379
|
+
throw new ConfigError("At least one target is required (editor, layer, or interactive)");
|
|
380
|
+
}
|
|
381
|
+
if (!config.version) {
|
|
382
|
+
throw new ConfigError("version is required in lumio.config.json");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/commands/build.ts
|
|
387
|
+
function registerBuild(program2) {
|
|
388
|
+
program2.command("build").description("Bundle extension as standalone HTML + source archive").action(async () => {
|
|
389
|
+
const configPath = path3.resolve("lumio.config.json");
|
|
390
|
+
if (!fs3.existsSync(configPath)) {
|
|
391
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
const configJson = fs3.readFileSync(configPath, "utf-8");
|
|
395
|
+
const config = parseConfigFile(configJson);
|
|
396
|
+
validateConfig(config);
|
|
397
|
+
const changelogPath = path3.resolve("CHANGELOG.md");
|
|
398
|
+
if (!fs3.existsSync(changelogPath)) {
|
|
399
|
+
console.warn("Warning: No CHANGELOG.md found. It is recommended to maintain a changelog.");
|
|
400
|
+
}
|
|
401
|
+
console.log(`Building ${config.name} v${config.version}...`);
|
|
402
|
+
const distDir = path3.resolve("dist");
|
|
403
|
+
fs3.mkdirSync(distDir, { recursive: true });
|
|
404
|
+
const { build } = await import("esbuild");
|
|
405
|
+
for (const target of config.targets) {
|
|
406
|
+
const entryPoint = path3.resolve("src", `${target}.tsx`);
|
|
407
|
+
if (!fs3.existsSync(entryPoint)) {
|
|
408
|
+
console.warn(`Warning: Entry point ${entryPoint} not found, skipping target "${target}".`);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
await build({
|
|
412
|
+
entryPoints: [entryPoint],
|
|
413
|
+
bundle: true,
|
|
414
|
+
minify: true,
|
|
415
|
+
format: "esm",
|
|
416
|
+
outfile: path3.join(distDir, `${target}.js`),
|
|
417
|
+
jsx: "automatic",
|
|
418
|
+
target: "es2022",
|
|
419
|
+
external: [],
|
|
420
|
+
define: {
|
|
421
|
+
"process.env.NODE_ENV": '"production"'
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
console.log(` Built: dist/${target}.js`);
|
|
425
|
+
}
|
|
426
|
+
const { default: archiver } = await import("archiver");
|
|
427
|
+
const sourceArchivePath = path3.join(distDir, "source.tar.gz");
|
|
428
|
+
const output = fs3.createWriteStream(sourceArchivePath);
|
|
429
|
+
const archive = archiver("tar", { gzip: true });
|
|
430
|
+
archive.pipe(output);
|
|
431
|
+
archive.directory("src/", "src");
|
|
432
|
+
if (config.server && fs3.existsSync("server")) {
|
|
433
|
+
archive.directory("server/", "server");
|
|
434
|
+
}
|
|
435
|
+
archive.file("lumio.config.json", { name: "lumio.config.json" });
|
|
436
|
+
archive.file("package.json", { name: "package.json" });
|
|
437
|
+
if (fs3.existsSync("CHANGELOG.md")) {
|
|
438
|
+
archive.file("CHANGELOG.md", { name: "CHANGELOG.md" });
|
|
439
|
+
}
|
|
440
|
+
await archive.finalize();
|
|
441
|
+
console.log(" Archive: dist/source.tar.gz");
|
|
442
|
+
console.log("Build complete.");
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/commands/deploy.ts
|
|
447
|
+
import * as fs4 from "fs";
|
|
448
|
+
import * as path4 from "path";
|
|
449
|
+
|
|
450
|
+
// src/changelog/parser.ts
|
|
451
|
+
var VERSION_HEADER_REGEX = /^## \[([^\]]+)\](?:\s*-\s*(\d{4}-\d{2}-\d{2}))?/;
|
|
452
|
+
function parseChangelog(markdown) {
|
|
453
|
+
const lines = markdown.split("\n");
|
|
454
|
+
const entries = [];
|
|
455
|
+
let currentEntry = null;
|
|
456
|
+
const contentLines = [];
|
|
457
|
+
for (const line of lines) {
|
|
458
|
+
const match = line.match(VERSION_HEADER_REGEX);
|
|
459
|
+
if (match) {
|
|
460
|
+
if (currentEntry) {
|
|
461
|
+
currentEntry.content = contentLines.join("\n").trim();
|
|
462
|
+
entries.push(currentEntry);
|
|
463
|
+
contentLines.length = 0;
|
|
464
|
+
}
|
|
465
|
+
currentEntry = {
|
|
466
|
+
version: match[1],
|
|
467
|
+
date: match[2] ?? null,
|
|
468
|
+
content: ""
|
|
469
|
+
};
|
|
470
|
+
} else if (currentEntry) {
|
|
471
|
+
contentLines.push(line);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (currentEntry) {
|
|
475
|
+
currentEntry.content = contentLines.join("\n").trim();
|
|
476
|
+
entries.push(currentEntry);
|
|
477
|
+
}
|
|
478
|
+
return entries;
|
|
479
|
+
}
|
|
480
|
+
function getVersionEntry(changelog, version) {
|
|
481
|
+
const entries = parseChangelog(changelog);
|
|
482
|
+
return entries.find((e) => e.version === version) ?? null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/commands/deploy.ts
|
|
486
|
+
function registerDeploy(program2) {
|
|
487
|
+
program2.command("deploy").description("Upload extension to Lumio (status: draft)").option("-v, --version <version>", "Version override").option("-d, --description <text>", "Release notes override").option("--breaking", "Mark as breaking change").option("--draft", "Explicitly mark as draft").action(async (options) => {
|
|
488
|
+
const store = new CredentialStore();
|
|
489
|
+
const token = store.getToken();
|
|
490
|
+
if (!token) {
|
|
491
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
const configPath = path4.resolve("lumio.config.json");
|
|
495
|
+
if (!fs4.existsSync(configPath)) {
|
|
496
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
const configJson = fs4.readFileSync(configPath, "utf-8");
|
|
500
|
+
const config = parseConfigFile(configJson);
|
|
501
|
+
validateConfig(config);
|
|
502
|
+
const version = options.version ?? config.version;
|
|
503
|
+
let releaseNotes = options.description ?? "";
|
|
504
|
+
if (!releaseNotes) {
|
|
505
|
+
const changelogPath = path4.resolve("CHANGELOG.md");
|
|
506
|
+
if (fs4.existsSync(changelogPath)) {
|
|
507
|
+
const changelog = fs4.readFileSync(changelogPath, "utf-8");
|
|
508
|
+
const entry = getVersionEntry(changelog, version);
|
|
509
|
+
if (entry) {
|
|
510
|
+
releaseNotes = entry.content;
|
|
511
|
+
} else if (!LumioApiClient.isCI()) {
|
|
512
|
+
console.error(`No changelog entry found for version ${version}.`);
|
|
513
|
+
console.error("Add an entry to CHANGELOG.md or use -d to provide release notes.");
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const distDir = path4.resolve("dist");
|
|
519
|
+
if (!fs4.existsSync(distDir)) {
|
|
520
|
+
console.error("No dist/ directory found. Run `lumio build` first.");
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
console.log(`Deploying ${config.name} v${version}...`);
|
|
524
|
+
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
525
|
+
const formData = new FormData();
|
|
526
|
+
const sourceArchive = path4.join(distDir, "source.tar.gz");
|
|
527
|
+
if (fs4.existsSync(sourceArchive)) {
|
|
528
|
+
const sourceBuffer = fs4.readFileSync(sourceArchive);
|
|
529
|
+
formData.append("source", new Blob([sourceBuffer]), "source.tar.gz");
|
|
530
|
+
}
|
|
531
|
+
formData.append("version", version);
|
|
532
|
+
formData.append("manifest", configJson);
|
|
533
|
+
formData.append("release_notes", releaseNotes);
|
|
534
|
+
formData.append("is_breaking", String(!!options.breaking));
|
|
535
|
+
try {
|
|
536
|
+
await client.uploadFile(
|
|
537
|
+
`/v1/developer/extensions/${config.extensionId}/versions`,
|
|
538
|
+
formData
|
|
539
|
+
);
|
|
540
|
+
console.log("Upload successful. Status: draft");
|
|
541
|
+
console.log("Submit for review via the dashboard or `lumio status`.");
|
|
542
|
+
if (LumioApiClient.isCI()) {
|
|
543
|
+
process.exit(0);
|
|
544
|
+
}
|
|
545
|
+
} catch (err) {
|
|
546
|
+
console.error("Deploy failed:", err);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/commands/status.ts
|
|
553
|
+
import * as fs5 from "fs";
|
|
554
|
+
import * as path5 from "path";
|
|
555
|
+
function registerStatus(program2) {
|
|
556
|
+
program2.command("status").description("Show review status of the latest version").action(async () => {
|
|
557
|
+
const store = new CredentialStore();
|
|
558
|
+
const token = store.getToken();
|
|
559
|
+
if (!token) {
|
|
560
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
const configPath = path5.resolve("lumio.config.json");
|
|
564
|
+
if (!fs5.existsSync(configPath)) {
|
|
565
|
+
console.error("lumio.config.json not found.");
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
const config = parseConfigFile(fs5.readFileSync(configPath, "utf-8"));
|
|
569
|
+
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
570
|
+
try {
|
|
571
|
+
const versions = await client.get(
|
|
572
|
+
`/v1/developer/extensions/${config.extensionId}/versions`
|
|
573
|
+
);
|
|
574
|
+
if (versions.length === 0) {
|
|
575
|
+
console.log("No versions deployed yet.");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const latest = versions[0];
|
|
579
|
+
console.log(`Extension: ${config.name}`);
|
|
580
|
+
console.log(`Version: ${latest.version}`);
|
|
581
|
+
console.log(`Status: ${latest.status}`);
|
|
582
|
+
console.log(`Uploaded: ${latest.created_at}`);
|
|
583
|
+
if (latest.reviewed_at) {
|
|
584
|
+
console.log(`Reviewed: ${latest.reviewed_at}`);
|
|
585
|
+
}
|
|
586
|
+
if (latest.review_notes) {
|
|
587
|
+
console.log(`Notes: ${latest.review_notes}`);
|
|
588
|
+
}
|
|
589
|
+
} catch (err) {
|
|
590
|
+
console.error("Failed to fetch status:", err);
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/commands/logs.ts
|
|
597
|
+
import * as fs6 from "fs";
|
|
598
|
+
import * as path6 from "path";
|
|
599
|
+
function registerLogs(program2) {
|
|
600
|
+
program2.command("logs").description("Stream server function logs via SSE").option("-f, --follow", "Follow log output", true).option("-n, --lines <count>", "Number of recent lines", "50").action(async (options) => {
|
|
601
|
+
const store = new CredentialStore();
|
|
602
|
+
const token = store.getToken();
|
|
603
|
+
if (!token) {
|
|
604
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
const configPath = path6.resolve("lumio.config.json");
|
|
608
|
+
if (!fs6.existsSync(configPath)) {
|
|
609
|
+
console.error("lumio.config.json not found.");
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
const config = parseConfigFile(fs6.readFileSync(configPath, "utf-8"));
|
|
613
|
+
const apiUrl = store.getApiUrl();
|
|
614
|
+
const logsUrl = `${apiUrl}/v1/developer/extensions/${config.extensionId}/logs?lines=${options.lines}`;
|
|
615
|
+
console.log(`Streaming logs for ${config.name}...`);
|
|
616
|
+
console.log("Press Ctrl+C to stop.\n");
|
|
617
|
+
try {
|
|
618
|
+
const eventsource = await import("eventsource");
|
|
619
|
+
const EventSource = "default" in eventsource ? eventsource.default : eventsource;
|
|
620
|
+
const es = new EventSource(logsUrl, {
|
|
621
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
622
|
+
});
|
|
623
|
+
es.onmessage = (event) => {
|
|
624
|
+
try {
|
|
625
|
+
const log = JSON.parse(event.data);
|
|
626
|
+
const timestamp = new Date(log.timestamp).toISOString();
|
|
627
|
+
const level = log.level?.toUpperCase() ?? "INFO";
|
|
628
|
+
console.log(`[${timestamp}] ${level}: ${log.message}`);
|
|
629
|
+
} catch {
|
|
630
|
+
console.log(event.data);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
es.onerror = () => {
|
|
634
|
+
console.error("Connection lost. Reconnecting...");
|
|
635
|
+
};
|
|
636
|
+
process.on("SIGINT", () => {
|
|
637
|
+
es.close();
|
|
638
|
+
console.log("\nLog streaming stopped.");
|
|
639
|
+
process.exit(0);
|
|
640
|
+
});
|
|
641
|
+
} catch (err) {
|
|
642
|
+
console.error("Failed to connect to log stream:", err);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/commands/env.ts
|
|
649
|
+
import * as fs7 from "fs";
|
|
650
|
+
import * as path7 from "path";
|
|
651
|
+
function validateSecretKey(key) {
|
|
652
|
+
return key.length > 0 && /^[A-Z0-9_]+$/.test(key);
|
|
653
|
+
}
|
|
654
|
+
function formatSecretList(secrets) {
|
|
655
|
+
if (secrets.length === 0) return "No secrets configured.";
|
|
656
|
+
return secrets.map((s) => ` ${s.key} (created ${s.created_at})`).join("\n");
|
|
657
|
+
}
|
|
658
|
+
function loadConfig() {
|
|
659
|
+
const configPath = path7.resolve("lumio.config.json");
|
|
660
|
+
if (!fs7.existsSync(configPath)) {
|
|
661
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
return parseConfigFile(fs7.readFileSync(configPath, "utf-8"));
|
|
665
|
+
}
|
|
666
|
+
function requireClient() {
|
|
667
|
+
const store = new CredentialStore();
|
|
668
|
+
const token = store.getToken();
|
|
669
|
+
if (!token) {
|
|
670
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
return new LumioApiClient(store.getApiUrl(), token);
|
|
674
|
+
}
|
|
675
|
+
function registerEnv(program2) {
|
|
676
|
+
const envCmd = program2.command("env").description("Manage server-side secrets for the extension");
|
|
677
|
+
envCmd.command("set <key> <value>").description("Set a secret (POST /v1/developer/extensions/{id}/secrets)").action(async (key, value) => {
|
|
678
|
+
if (!validateSecretKey(key)) {
|
|
679
|
+
console.error(
|
|
680
|
+
"Invalid key format. Use uppercase letters, numbers, and underscores only (e.g. API_KEY)."
|
|
681
|
+
);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
const config = loadConfig();
|
|
685
|
+
const client = requireClient();
|
|
686
|
+
await client.post(`/v1/developer/extensions/${config.extensionId}/secrets`, { key, value });
|
|
687
|
+
console.log(`Secret ${key} set.`);
|
|
688
|
+
});
|
|
689
|
+
envCmd.command("list").description("List all secrets (keys only, no values)").action(async () => {
|
|
690
|
+
const config = loadConfig();
|
|
691
|
+
const client = requireClient();
|
|
692
|
+
const secrets = await client.get(
|
|
693
|
+
`/v1/developer/extensions/${config.extensionId}/secrets`
|
|
694
|
+
);
|
|
695
|
+
console.log(formatSecretList(secrets));
|
|
696
|
+
});
|
|
697
|
+
envCmd.command("delete <key>").description("Delete a secret").action(async (key) => {
|
|
698
|
+
const config = loadConfig();
|
|
699
|
+
const client = requireClient();
|
|
700
|
+
await client.delete(`/v1/developer/extensions/${config.extensionId}/secrets/${key}`);
|
|
701
|
+
console.log(`Secret ${key} deleted.`);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/commands/run.ts
|
|
706
|
+
import * as fs8 from "fs";
|
|
707
|
+
import * as path8 from "path";
|
|
708
|
+
function parseFunctionRef(ref) {
|
|
709
|
+
const colonIndex = ref.indexOf(":");
|
|
710
|
+
if (colonIndex === -1) {
|
|
711
|
+
throw new Error("Invalid function reference. Use format: query:functionName");
|
|
712
|
+
}
|
|
713
|
+
const type = ref.slice(0, colonIndex);
|
|
714
|
+
const name = ref.slice(colonIndex + 1);
|
|
715
|
+
if (!["query", "mutation", "action"].includes(type)) {
|
|
716
|
+
throw new Error(`Function type must be query, mutation, or action. Got: "${type}"`);
|
|
717
|
+
}
|
|
718
|
+
return { type, name };
|
|
719
|
+
}
|
|
720
|
+
function registerRun(program2) {
|
|
721
|
+
program2.command("run <type:name> [args]").description("Execute a server function locally (e.g. lumio run query:getPlayers)").action(async (typeAndName, argsJson) => {
|
|
722
|
+
const fnRef = parseFunctionRef(typeAndName);
|
|
723
|
+
const configPath = path8.resolve("lumio.config.json");
|
|
724
|
+
if (!fs8.existsSync(configPath)) {
|
|
725
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
const config = parseConfigFile(fs8.readFileSync(configPath, "utf-8"));
|
|
729
|
+
const store = new CredentialStore();
|
|
730
|
+
const token = store.getToken();
|
|
731
|
+
if (!token) {
|
|
732
|
+
console.error("Not logged in. Run `lumio login` first.");
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
735
|
+
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
736
|
+
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
737
|
+
try {
|
|
738
|
+
const result = await client.post(
|
|
739
|
+
`/v1/extension-installs/${config.extensionId}/execute/${fnRef.name}`,
|
|
740
|
+
{ type: fnRef.type, args }
|
|
741
|
+
);
|
|
742
|
+
console.log(JSON.stringify(result, null, 2));
|
|
743
|
+
} catch (err) {
|
|
744
|
+
console.error("Function execution failed:", err);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/commands/dashboard.ts
|
|
751
|
+
import * as fs9 from "fs";
|
|
752
|
+
import * as path9 from "path";
|
|
753
|
+
function registerDashboard(program2) {
|
|
754
|
+
program2.command("dashboard").description("Open the extension dashboard in browser").action(async () => {
|
|
755
|
+
const configPath = path9.resolve("lumio.config.json");
|
|
756
|
+
if (!fs9.existsSync(configPath)) {
|
|
757
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
const config = parseConfigFile(fs9.readFileSync(configPath, "utf-8"));
|
|
761
|
+
const baseUrl = process.env.LUMIO_WEB_URL ?? "https://lumio.vision";
|
|
762
|
+
const url = `${baseUrl}/dashboard/extensions/${config.extensionId}`;
|
|
763
|
+
console.log(`Opening ${url}...`);
|
|
764
|
+
const { default: open } = await import("open");
|
|
765
|
+
await open(url);
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/program.ts
|
|
770
|
+
function createProgram() {
|
|
771
|
+
const program2 = new Command();
|
|
772
|
+
program2.name("lumio");
|
|
773
|
+
program2.description("Lumio Extension CLI -- Build, test, and deploy Lumio extensions");
|
|
774
|
+
program2.version("2026.5.1");
|
|
775
|
+
registerLogin(program2);
|
|
776
|
+
registerLogout(program2);
|
|
777
|
+
registerWhoami(program2);
|
|
778
|
+
registerInit(program2);
|
|
779
|
+
registerDev(program2);
|
|
780
|
+
registerBuild(program2);
|
|
781
|
+
registerDeploy(program2);
|
|
782
|
+
registerStatus(program2);
|
|
783
|
+
registerLogs(program2);
|
|
784
|
+
registerEnv(program2);
|
|
785
|
+
registerRun(program2);
|
|
786
|
+
registerDashboard(program2);
|
|
787
|
+
return program2;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/index.ts
|
|
791
|
+
var program = createProgram();
|
|
792
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
793
|
+
console.error(err);
|
|
794
|
+
process.exit(1);
|
|
795
|
+
});
|
|
796
|
+
export {
|
|
797
|
+
CredentialStore,
|
|
798
|
+
LumioApiClient,
|
|
799
|
+
createProgram
|
|
800
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zaflun/lumio-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lumio Extension CLI — Build, test, and deploy Lumio extensions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"lumio": "bin/lumio.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"bin",
|
|
14
|
+
"templates"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"archiver": "^7.0.1",
|
|
18
|
+
"chalk": "^5.4.1",
|
|
19
|
+
"commander": "^13.1.0",
|
|
20
|
+
"esbuild": "^0.25.4",
|
|
21
|
+
"eventsource": "^3.0.6",
|
|
22
|
+
"inquirer": "^12.6.0",
|
|
23
|
+
"open": "^10.1.2",
|
|
24
|
+
"ora": "^8.2.0",
|
|
25
|
+
"@zaflun/lumio-extension-types": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/archiver": "^6.0.3",
|
|
29
|
+
"@types/eventsource": "^1.1.15",
|
|
30
|
+
"typescript": "^5.8.3",
|
|
31
|
+
"vitest": "^3.1.4",
|
|
32
|
+
"tsup": "^8.4.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/zaflun/lumio.git",
|
|
38
|
+
"directory": "packages/cli"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"author": {
|
|
44
|
+
"name": "zaflun",
|
|
45
|
+
"email": "developers@zaflun.com",
|
|
46
|
+
"url": "https://withzaflun.com"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://developers.lumio.vision",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/zaflun/lumio/issues",
|
|
51
|
+
"email": "developers@zaflun.com"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
55
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"lint": "eslint src/",
|
|
59
|
+
"typecheck": "tsc --noEmit"
|
|
60
|
+
}
|
|
61
|
+
}
|