@zaflun/lumio-cli 0.1.0 → 0.2.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/dist/index.js +76 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/program.ts
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
|
|
4
|
+
// src/commands/login.ts
|
|
5
|
+
import * as http from "http";
|
|
6
|
+
import * as crypto from "crypto";
|
|
7
|
+
|
|
4
8
|
// src/auth/credential-store.ts
|
|
5
9
|
import * as fs from "fs";
|
|
6
10
|
import * as path from "path";
|
|
@@ -54,6 +58,11 @@ var CredentialStore = class {
|
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
// src/commands/login.ts
|
|
61
|
+
function generatePkceChallenge() {
|
|
62
|
+
const verifier = crypto.randomBytes(32).toString("base64url");
|
|
63
|
+
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
|
|
64
|
+
return { verifier, challenge };
|
|
65
|
+
}
|
|
57
66
|
function registerLogin(program2) {
|
|
58
67
|
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
68
|
const store = new CredentialStore();
|
|
@@ -62,12 +71,69 @@ function registerLogin(program2) {
|
|
|
62
71
|
console.log("Authenticated with token.");
|
|
63
72
|
return;
|
|
64
73
|
}
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
const { verifier, challenge } = generatePkceChallenge();
|
|
75
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
76
|
+
const port = 9876;
|
|
77
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
78
|
+
await new Promise((resolve9, reject) => {
|
|
79
|
+
const server = http.createServer(async (req, res) => {
|
|
80
|
+
if (!req.url?.startsWith("/callback")) {
|
|
81
|
+
res.writeHead(404);
|
|
82
|
+
res.end();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
86
|
+
const code = url.searchParams.get("code");
|
|
87
|
+
const returnedState = url.searchParams.get("state");
|
|
88
|
+
if (returnedState !== state || !code) {
|
|
89
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
90
|
+
res.end("<html><body><h2>Authentication failed.</h2><p>Invalid state or missing code. You can close this window.</p></body></html>");
|
|
91
|
+
server.close();
|
|
92
|
+
reject(new Error("Authentication failed: invalid state or missing code"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const tokenRes = await fetch(`${options.apiUrl}/v1/auth/cli/token`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
grant_type: "authorization_code",
|
|
101
|
+
code,
|
|
102
|
+
redirect_uri: redirectUri,
|
|
103
|
+
code_verifier: verifier
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
if (!tokenRes.ok) {
|
|
107
|
+
throw new Error(`Token exchange failed: ${tokenRes.status}`);
|
|
108
|
+
}
|
|
109
|
+
const tokenData = await tokenRes.json();
|
|
110
|
+
store.saveToken(tokenData.access_token, options.apiUrl);
|
|
111
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
112
|
+
res.end("<html><body><h2>Authenticated!</h2><p>You can close this window and return to the CLI.</p></body></html>");
|
|
113
|
+
} catch (e) {
|
|
114
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
115
|
+
res.end("<html><body><h2>Authentication failed.</h2><p>Token exchange error. You can close this window.</p></body></html>");
|
|
116
|
+
server.close();
|
|
117
|
+
reject(e);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
server.close();
|
|
121
|
+
resolve9();
|
|
122
|
+
});
|
|
123
|
+
server.listen(port, async () => {
|
|
124
|
+
const pkceUrl = `${options.apiUrl}/v1/auth/cli/authorize?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
|
|
125
|
+
console.log("Opening browser for authentication...");
|
|
126
|
+
const { default: open } = await import("open");
|
|
127
|
+
await open(pkceUrl);
|
|
128
|
+
console.log("Waiting for authentication callback...");
|
|
129
|
+
console.log("If the browser did not open, visit:", pkceUrl);
|
|
130
|
+
});
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
server.close();
|
|
133
|
+
reject(new Error("Authentication timed out after 120 seconds"));
|
|
134
|
+
}, 12e4);
|
|
135
|
+
});
|
|
136
|
+
console.log("Authenticated successfully.");
|
|
71
137
|
});
|
|
72
138
|
}
|
|
73
139
|
|
|
@@ -253,7 +319,7 @@ function registerInit(program2) {
|
|
|
253
319
|
const projectDir = path2.resolve(answers.name.toLowerCase().replace(/\s+/g, "-"));
|
|
254
320
|
fs2.mkdirSync(projectDir, { recursive: true });
|
|
255
321
|
const config = {
|
|
256
|
-
$schema: "https://
|
|
322
|
+
$schema: "https://lumio.vision/schemas/lumio.config.schema.json",
|
|
257
323
|
extensionId: extension.id,
|
|
258
324
|
name: answers.name,
|
|
259
325
|
description: answers.description,
|
|
@@ -528,8 +594,10 @@ function registerDeploy(program2) {
|
|
|
528
594
|
const sourceBuffer = fs4.readFileSync(sourceArchive);
|
|
529
595
|
formData.append("source", new Blob([sourceBuffer]), "source.tar.gz");
|
|
530
596
|
}
|
|
597
|
+
const manifest = JSON.parse(configJson);
|
|
598
|
+
manifest.version = version;
|
|
531
599
|
formData.append("version", version);
|
|
532
|
-
formData.append("manifest",
|
|
600
|
+
formData.append("manifest", JSON.stringify(manifest));
|
|
533
601
|
formData.append("release_notes", releaseNotes);
|
|
534
602
|
formData.append("is_breaking", String(!!options.breaking));
|
|
535
603
|
try {
|