drop2run 0.1.2 → 0.2.1
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 +3 -1
- package/dist/index.js +233 -166
- package/dist/{package-RiuDiPsO.js → package-D-UHpX_a.js} +8 -2
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -72,7 +72,9 @@ DROP2RUN_TOKEN=d2r_...
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
The environment wins over `~/.config/drop2run/config.json`, and that is the same file and precedence
|
|
75
|
-
`@drop2run/mcp` uses, so signing in once covers both.
|
|
75
|
+
`@drop2run/mcp` uses, so signing in once covers both — in either direction. That server runs the same
|
|
76
|
+
two flows from its own `login` and `login_code` tools, so a sign-in done in a chat leaves this command
|
|
77
|
+
line signed in too.
|
|
76
78
|
|
|
77
79
|
`drop2run where` says which source is in force without printing the token, so "why is it using the wrong
|
|
78
80
|
account" is answerable in an issue report or a CI log.
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { join, dirname, relative, sep, resolve } from "node:path";
|
|
2
2
|
import { readFileSync, writeFileSync, chmodSync, renameSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { homedir, hostname } from "node:os";
|
|
4
|
-
import { realpath, readdir, stat, readFile } from "node:fs/promises";
|
|
5
4
|
import { spawn } from "node:child_process";
|
|
6
5
|
import { randomBytes, createHash } from "node:crypto";
|
|
7
6
|
import { createServer } from "node:http";
|
|
7
|
+
import { realpath, readdir, stat, readFile } from "node:fs/promises";
|
|
8
8
|
async function call(credentials, path, init2 = {}) {
|
|
9
9
|
const response = await fetch(`${credentials.apiBaseUrl}/${path}`, {
|
|
10
10
|
...init2,
|
|
@@ -125,20 +125,188 @@ function clearToken(path = configPath()) {
|
|
|
125
125
|
renameSync(temporary, path);
|
|
126
126
|
return true;
|
|
127
127
|
}
|
|
128
|
-
function missingCredentialsMessage() {
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
"",
|
|
132
|
-
"Run `drop2run login` to sign in through a browser, which stores one for you.",
|
|
128
|
+
function missingCredentialsMessage(surface = "cli") {
|
|
129
|
+
const signIn = surface === "mcp" ? [
|
|
130
|
+
"Call the `login` tool to sign in through a browser, which stores a token.",
|
|
133
131
|
"",
|
|
132
|
+
"Where no browser can be opened on this machine — a container, a remote host — call",
|
|
133
|
+
"`login_code` instead and pass on the short code it gives you."
|
|
134
|
+
] : ["Run `drop2run login` to sign in through a browser, which stores one for you."];
|
|
135
|
+
const byHand = surface === "mcp" ? [
|
|
136
|
+
"To set one without signing in here, create a token at",
|
|
137
|
+
"https://dropto.run/account/tokens and either:",
|
|
138
|
+
` - put it in ${configPath()} as {"token": "d2r_..."}, which this`,
|
|
139
|
+
" server reads on every call and needs no restart, or",
|
|
140
|
+
` - set ${TOKEN_VARIABLE} in the environment and restart this server, since it reads`,
|
|
141
|
+
" the environment it was started in."
|
|
142
|
+
] : [
|
|
134
143
|
"For CI, or where no browser can be opened, create a token at",
|
|
135
144
|
"https://dropto.run/account/tokens and either:",
|
|
136
145
|
` - set ${TOKEN_VARIABLE} in the environment, or`,
|
|
137
|
-
` - put it in ${configPath()} as {"token": "d2r_..."}
|
|
146
|
+
` - put it in ${configPath()} as {"token": "d2r_..."}`
|
|
147
|
+
];
|
|
148
|
+
return [
|
|
149
|
+
"No Drop2Run access token.",
|
|
150
|
+
"",
|
|
151
|
+
...signIn,
|
|
152
|
+
"",
|
|
153
|
+
...byHand,
|
|
138
154
|
"",
|
|
139
155
|
"The token is shown once when it is created and cannot be recovered afterwards."
|
|
140
156
|
].join("\n");
|
|
141
157
|
}
|
|
158
|
+
const TIMEOUT_MS = 5 * 60 * 1e3;
|
|
159
|
+
function newAttempt() {
|
|
160
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
161
|
+
return {
|
|
162
|
+
verifier,
|
|
163
|
+
challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
164
|
+
state: randomBytes(16).toString("base64url")
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function clientName(surface) {
|
|
168
|
+
const clean2 = (value) => value.trim().replace(/[^A-Za-z0-9._-]/g, "-");
|
|
169
|
+
const label = "";
|
|
170
|
+
const raw = clean2(hostname()).slice(0, 64 - label.length);
|
|
171
|
+
return `${raw === "" ? "drop2run" : raw}${label}`;
|
|
172
|
+
}
|
|
173
|
+
function openBrowser(url) {
|
|
174
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
175
|
+
try {
|
|
176
|
+
const child = spawn(command, [...args], { stdio: "ignore", detached: true });
|
|
177
|
+
child.unref();
|
|
178
|
+
child.on("error", () => {
|
|
179
|
+
});
|
|
180
|
+
return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function listen(state) {
|
|
186
|
+
return new Promise((resolveListener, rejectListener) => {
|
|
187
|
+
let settle = null;
|
|
188
|
+
let fail = null;
|
|
189
|
+
const received = new Promise((resolveCode, rejectCode) => {
|
|
190
|
+
settle = resolveCode;
|
|
191
|
+
fail = rejectCode;
|
|
192
|
+
});
|
|
193
|
+
const server = createServer((request2, response) => {
|
|
194
|
+
const url = new URL(request2.url ?? "/", "http://127.0.0.1");
|
|
195
|
+
if (url.pathname !== "/cb") {
|
|
196
|
+
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
197
|
+
response.end("Not found.\n");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const code = url.searchParams.get("code") ?? "";
|
|
201
|
+
const returned = url.searchParams.get("state") ?? "";
|
|
202
|
+
if (returned !== state || code === "") {
|
|
203
|
+
respond(
|
|
204
|
+
response,
|
|
205
|
+
400,
|
|
206
|
+
"Sign-in could not be completed",
|
|
207
|
+
"This did not come from the sign-in that was started here. Nothing has been changed."
|
|
208
|
+
);
|
|
209
|
+
fail?.(new Error("The browser returned a response that did not match this sign-in."));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
respond(response, 200, "Signed in", "You can close this tab now.");
|
|
213
|
+
settle?.({ code });
|
|
214
|
+
});
|
|
215
|
+
server.on("error", (error) => {
|
|
216
|
+
rejectListener(error);
|
|
217
|
+
fail?.(error instanceof Error ? error : new Error(String(error)));
|
|
218
|
+
});
|
|
219
|
+
server.listen(0, "127.0.0.1", () => {
|
|
220
|
+
const address = server.address();
|
|
221
|
+
if (address === null) {
|
|
222
|
+
rejectListener(new Error("Could not open a local port to receive the sign-in."));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
resolveListener({
|
|
226
|
+
port: address.port,
|
|
227
|
+
received,
|
|
228
|
+
close: () => server.close()
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function respond(response, status, title, detail) {
|
|
234
|
+
response.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
235
|
+
response.end(
|
|
236
|
+
`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${title} — drop2run</title><style>body{font:16px/1.6 system-ui,sans-serif;margin:20vh auto;max-width:28rem;padding:0 1.5rem;color:#12211f}h1{font-size:1.4rem;margin:0 0 .5rem}p{margin:0;color:#64706e}@media(prefers-color-scheme:dark){body{background:#0d1413;color:#e8efee}p{color:#9aa8a6}}</style></head><body><h1>${title}</h1><p>${detail}</p></body></html>
|
|
237
|
+
`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
async function waitForCallback(received, timeoutMs = TIMEOUT_MS) {
|
|
241
|
+
let timer;
|
|
242
|
+
try {
|
|
243
|
+
return await Promise.race([
|
|
244
|
+
received,
|
|
245
|
+
new Promise((_, reject) => {
|
|
246
|
+
timer = setTimeout(
|
|
247
|
+
() => reject(new Error("Timed out waiting for the browser. Nothing has been changed.")),
|
|
248
|
+
timeoutMs
|
|
249
|
+
);
|
|
250
|
+
})
|
|
251
|
+
]);
|
|
252
|
+
} finally {
|
|
253
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function consentUrl(dashboardUrl, attempt, port, name) {
|
|
257
|
+
const url = new URL("/cli-auth", dashboardUrl);
|
|
258
|
+
url.searchParams.set("challenge", attempt.challenge);
|
|
259
|
+
url.searchParams.set("state", attempt.state);
|
|
260
|
+
url.searchParams.set("port", String(port));
|
|
261
|
+
url.searchParams.set("name", name);
|
|
262
|
+
return url.toString();
|
|
263
|
+
}
|
|
264
|
+
async function startDevice(apiBaseUrl, clientName2) {
|
|
265
|
+
const response = await fetch(`${apiBaseUrl}/auth/cli/device`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "Content-Type": "application/json" },
|
|
268
|
+
body: JSON.stringify({ clientName: clientName2 })
|
|
269
|
+
});
|
|
270
|
+
if (!response.ok) throw new Error(await problemDetail(response));
|
|
271
|
+
return await response.json();
|
|
272
|
+
}
|
|
273
|
+
async function pollDevice(apiBaseUrl, deviceCode) {
|
|
274
|
+
let response;
|
|
275
|
+
try {
|
|
276
|
+
response = await fetch(`${apiBaseUrl}/auth/cli/device/token`, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: { "Content-Type": "application/json" },
|
|
279
|
+
body: JSON.stringify({ deviceCode })
|
|
280
|
+
});
|
|
281
|
+
} catch {
|
|
282
|
+
return { state: "pending" };
|
|
283
|
+
}
|
|
284
|
+
if (response.ok) {
|
|
285
|
+
const body = await response.json();
|
|
286
|
+
return { state: "granted", token: { token: body.token, name: body.name, email: body.email } };
|
|
287
|
+
}
|
|
288
|
+
const problem = await response.json().catch(() => null);
|
|
289
|
+
if (problem?.type === "authorization_pending") return { state: "pending" };
|
|
290
|
+
if (problem?.type === "slow_down") return { state: "slow_down" };
|
|
291
|
+
return {
|
|
292
|
+
state: "dead",
|
|
293
|
+
message: problem?.detail ?? `The API answered ${response.status}.`
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function problemDetail(response) {
|
|
297
|
+
const problem = await response.json().catch(() => null);
|
|
298
|
+
return problem?.detail ?? `The API answered ${response.status}.`;
|
|
299
|
+
}
|
|
300
|
+
async function exchange(apiBaseUrl, code, verifier) {
|
|
301
|
+
const response = await fetch(`${apiBaseUrl}/auth/cli/token`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: { "Content-Type": "application/json" },
|
|
304
|
+
body: JSON.stringify({ code, verifier })
|
|
305
|
+
});
|
|
306
|
+
if (!response.ok) throw new Error(await problemDetail(response));
|
|
307
|
+
const body = await response.json();
|
|
308
|
+
return { token: body.token, name: body.name, email: body.email };
|
|
309
|
+
}
|
|
142
310
|
const PROJECT_FILE = "drop2run.json";
|
|
143
311
|
function projectPath(directory = process.cwd()) {
|
|
144
312
|
return join(directory, PROJECT_FILE);
|
|
@@ -299,6 +467,56 @@ function isDocumentPath(path) {
|
|
|
299
467
|
const lower = path.toLowerCase();
|
|
300
468
|
return DOCUMENT_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
301
469
|
}
|
|
470
|
+
const VIEWABLE_EXTENSIONS = [
|
|
471
|
+
// Documents — these also count at any depth, via DOCUMENT_EXTENSIONS.
|
|
472
|
+
".md",
|
|
473
|
+
".markdown",
|
|
474
|
+
".pdf",
|
|
475
|
+
// Prose the reader shows preformatted.
|
|
476
|
+
".txt",
|
|
477
|
+
".log",
|
|
478
|
+
".csv",
|
|
479
|
+
// Source the reader shows with highlighting.
|
|
480
|
+
".json",
|
|
481
|
+
".yaml",
|
|
482
|
+
".yml",
|
|
483
|
+
".toml",
|
|
484
|
+
".css",
|
|
485
|
+
".js",
|
|
486
|
+
".ts",
|
|
487
|
+
".tsx",
|
|
488
|
+
".jsx",
|
|
489
|
+
".sh",
|
|
490
|
+
".xml",
|
|
491
|
+
// Pictures the reader shows with an img element.
|
|
492
|
+
".png",
|
|
493
|
+
".jpg",
|
|
494
|
+
".jpeg",
|
|
495
|
+
".gif",
|
|
496
|
+
".webp",
|
|
497
|
+
".avif",
|
|
498
|
+
".svg",
|
|
499
|
+
".ico",
|
|
500
|
+
// See the note above: a floor for a caller that did not rename it.
|
|
501
|
+
".html",
|
|
502
|
+
".htm"
|
|
503
|
+
];
|
|
504
|
+
function isViewablePath(path) {
|
|
505
|
+
const lower = path.toLowerCase();
|
|
506
|
+
return VIEWABLE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
507
|
+
}
|
|
508
|
+
function canPublish(paths) {
|
|
509
|
+
if (paths.some((path) => path === REQUIRED_INDEX)) return true;
|
|
510
|
+
if (paths.some(isDocumentPath)) return true;
|
|
511
|
+
return paths.length === 1 && paths[0] !== void 0 && isViewablePath(paths[0]);
|
|
512
|
+
}
|
|
513
|
+
function renameOfLoneHtmlPage(paths) {
|
|
514
|
+
if (paths.length !== 1) return null;
|
|
515
|
+
const [only] = paths;
|
|
516
|
+
if (only === void 0 || only === REQUIRED_INDEX) return null;
|
|
517
|
+
const lower = only.toLowerCase();
|
|
518
|
+
return lower.endsWith(".html") || lower.endsWith(".htm") ? only : null;
|
|
519
|
+
}
|
|
302
520
|
function formatBytes(bytes) {
|
|
303
521
|
if (bytes < 1024) return `${bytes} B`;
|
|
304
522
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -312,11 +530,10 @@ function checkLimits(files, limits) {
|
|
|
312
530
|
"That drop contained no files to deploy. Folders like .git and node_modules are skipped."
|
|
313
531
|
);
|
|
314
532
|
}
|
|
315
|
-
|
|
316
|
-
if (!hasIndex && !files.some((file) => isDocumentPath(file.path))) {
|
|
533
|
+
if (!canPublish(files.map((file) => file.path))) {
|
|
317
534
|
throw new DeployError(
|
|
318
535
|
ClientErrorCode.MissingIndex,
|
|
319
|
-
`A site needs an ${REQUIRED_INDEX} at its top level, or at least one .md or .pdf file to publish as a documents site. Drop the folder that contains it, not the folder above.`
|
|
536
|
+
`A site needs an ${REQUIRED_INDEX} at its top level, or at least one .md or .pdf file to publish as a documents site — or be a single file that can be displayed on its own, such as a note, a PDF, a page or an image. Drop the folder that contains it, not the folder above.`
|
|
320
537
|
);
|
|
321
538
|
}
|
|
322
539
|
if (!limits) return;
|
|
@@ -327,9 +544,7 @@ function checkLimits(files, limits) {
|
|
|
327
544
|
{ limit: limits.maxFiles, actual: files.length }
|
|
328
545
|
);
|
|
329
546
|
}
|
|
330
|
-
const oversized = files.find(
|
|
331
|
-
(file) => file.bytes.length > limits.maxFileBytes
|
|
332
|
-
);
|
|
547
|
+
const oversized = files.find((file) => file.bytes.length > limits.maxFileBytes);
|
|
333
548
|
if (oversized) {
|
|
334
549
|
throw new DeployError(
|
|
335
550
|
ClientErrorCode.FileTooLarge,
|
|
@@ -558,8 +773,10 @@ function asDeployError(error) {
|
|
|
558
773
|
}
|
|
559
774
|
async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
560
775
|
try {
|
|
561
|
-
const
|
|
562
|
-
onProgress({ type: "collecting", files:
|
|
776
|
+
const collected = await source(signal);
|
|
777
|
+
onProgress({ type: "collecting", files: collected.length });
|
|
778
|
+
const rename = renameOfLoneHtmlPage(collected.map((file) => file.path));
|
|
779
|
+
const files = rename === null ? collected : collected.map((file) => file.path === rename ? { ...file, path: "index.html" } : file);
|
|
563
780
|
const hashed = await hashAll(
|
|
564
781
|
files,
|
|
565
782
|
(done, total) => onProgress({ type: "hashing", done, total }),
|
|
@@ -694,156 +911,6 @@ async function publish(credentials, source, site) {
|
|
|
694
911
|
function publishDirectory(credentials, directory, site) {
|
|
695
912
|
return publish(credentials, directorySource(directory), site);
|
|
696
913
|
}
|
|
697
|
-
const TIMEOUT_MS = 5 * 60 * 1e3;
|
|
698
|
-
function newAttempt() {
|
|
699
|
-
const verifier = randomBytes(32).toString("base64url");
|
|
700
|
-
return {
|
|
701
|
-
verifier,
|
|
702
|
-
challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
703
|
-
state: randomBytes(16).toString("base64url")
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
function clientName() {
|
|
707
|
-
const raw = hostname().trim().replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 64);
|
|
708
|
-
return raw === "" ? "drop2run-cli" : raw;
|
|
709
|
-
}
|
|
710
|
-
function openBrowser(url) {
|
|
711
|
-
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
712
|
-
try {
|
|
713
|
-
const child = spawn(command, [...args], { stdio: "ignore", detached: true });
|
|
714
|
-
child.unref();
|
|
715
|
-
child.on("error", () => {
|
|
716
|
-
});
|
|
717
|
-
return true;
|
|
718
|
-
} catch {
|
|
719
|
-
return false;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
function listen(state) {
|
|
723
|
-
return new Promise((resolveListener, rejectListener) => {
|
|
724
|
-
let settle = null;
|
|
725
|
-
let fail = null;
|
|
726
|
-
const received = new Promise((resolveCode, rejectCode) => {
|
|
727
|
-
settle = resolveCode;
|
|
728
|
-
fail = rejectCode;
|
|
729
|
-
});
|
|
730
|
-
const server = createServer((request2, response) => {
|
|
731
|
-
const url = new URL(request2.url ?? "/", "http://127.0.0.1");
|
|
732
|
-
if (url.pathname !== "/cb") {
|
|
733
|
-
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
734
|
-
response.end("Not found.\n");
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
const code = url.searchParams.get("code") ?? "";
|
|
738
|
-
const returned = url.searchParams.get("state") ?? "";
|
|
739
|
-
if (returned !== state || code === "") {
|
|
740
|
-
respond(
|
|
741
|
-
response,
|
|
742
|
-
400,
|
|
743
|
-
"Sign-in could not be completed",
|
|
744
|
-
"This did not come from the sign-in that was started here. Nothing has been changed."
|
|
745
|
-
);
|
|
746
|
-
fail?.(new Error("The browser returned a response that did not match this sign-in."));
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
respond(response, 200, "Signed in", "You can close this tab and go back to the terminal.");
|
|
750
|
-
settle?.({ code });
|
|
751
|
-
});
|
|
752
|
-
server.on("error", (error) => {
|
|
753
|
-
rejectListener(error);
|
|
754
|
-
fail?.(error instanceof Error ? error : new Error(String(error)));
|
|
755
|
-
});
|
|
756
|
-
server.listen(0, "127.0.0.1", () => {
|
|
757
|
-
const address = server.address();
|
|
758
|
-
if (address === null) {
|
|
759
|
-
rejectListener(new Error("Could not open a local port to receive the sign-in."));
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
resolveListener({
|
|
763
|
-
port: address.port,
|
|
764
|
-
received,
|
|
765
|
-
close: () => server.close()
|
|
766
|
-
});
|
|
767
|
-
});
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
function respond(response, status, title, detail) {
|
|
771
|
-
response.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
772
|
-
response.end(
|
|
773
|
-
`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${title} — drop2run</title><style>body{font:16px/1.6 system-ui,sans-serif;margin:20vh auto;max-width:28rem;padding:0 1.5rem;color:#12211f}h1{font-size:1.4rem;margin:0 0 .5rem}p{margin:0;color:#64706e}@media(prefers-color-scheme:dark){body{background:#0d1413;color:#e8efee}p{color:#9aa8a6}}</style></head><body><h1>${title}</h1><p>${detail}</p></body></html>
|
|
774
|
-
`
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
async function waitForCallback(received, timeoutMs = TIMEOUT_MS) {
|
|
778
|
-
let timer;
|
|
779
|
-
try {
|
|
780
|
-
return await Promise.race([
|
|
781
|
-
received,
|
|
782
|
-
new Promise((_, reject) => {
|
|
783
|
-
timer = setTimeout(
|
|
784
|
-
() => reject(new Error("Timed out waiting for the browser. Nothing has been changed.")),
|
|
785
|
-
timeoutMs
|
|
786
|
-
);
|
|
787
|
-
})
|
|
788
|
-
]);
|
|
789
|
-
} finally {
|
|
790
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
function consentUrl(dashboardUrl, attempt, port, name) {
|
|
794
|
-
const url = new URL("/cli-auth", dashboardUrl);
|
|
795
|
-
url.searchParams.set("challenge", attempt.challenge);
|
|
796
|
-
url.searchParams.set("state", attempt.state);
|
|
797
|
-
url.searchParams.set("port", String(port));
|
|
798
|
-
url.searchParams.set("name", name);
|
|
799
|
-
return url.toString();
|
|
800
|
-
}
|
|
801
|
-
async function startDevice(apiBaseUrl, clientName2) {
|
|
802
|
-
const response = await fetch(`${apiBaseUrl}/auth/cli/device`, {
|
|
803
|
-
method: "POST",
|
|
804
|
-
headers: { "Content-Type": "application/json" },
|
|
805
|
-
body: JSON.stringify({ clientName: clientName2 })
|
|
806
|
-
});
|
|
807
|
-
if (!response.ok) throw new Error(await problemDetail(response));
|
|
808
|
-
return await response.json();
|
|
809
|
-
}
|
|
810
|
-
async function pollDevice(apiBaseUrl, deviceCode) {
|
|
811
|
-
let response;
|
|
812
|
-
try {
|
|
813
|
-
response = await fetch(`${apiBaseUrl}/auth/cli/device/token`, {
|
|
814
|
-
method: "POST",
|
|
815
|
-
headers: { "Content-Type": "application/json" },
|
|
816
|
-
body: JSON.stringify({ deviceCode })
|
|
817
|
-
});
|
|
818
|
-
} catch {
|
|
819
|
-
return { state: "pending" };
|
|
820
|
-
}
|
|
821
|
-
if (response.ok) {
|
|
822
|
-
const body = await response.json();
|
|
823
|
-
return { state: "granted", token: { token: body.token, name: body.name, email: body.email } };
|
|
824
|
-
}
|
|
825
|
-
const problem = await response.json().catch(() => null);
|
|
826
|
-
if (problem?.type === "authorization_pending") return { state: "pending" };
|
|
827
|
-
if (problem?.type === "slow_down") return { state: "slow_down" };
|
|
828
|
-
return {
|
|
829
|
-
state: "dead",
|
|
830
|
-
message: problem?.detail ?? `The API answered ${response.status}.`
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
async function problemDetail(response) {
|
|
834
|
-
const problem = await response.json().catch(() => null);
|
|
835
|
-
return problem?.detail ?? `The API answered ${response.status}.`;
|
|
836
|
-
}
|
|
837
|
-
async function exchange(apiBaseUrl, code, verifier) {
|
|
838
|
-
const response = await fetch(`${apiBaseUrl}/auth/cli/token`, {
|
|
839
|
-
method: "POST",
|
|
840
|
-
headers: { "Content-Type": "application/json" },
|
|
841
|
-
body: JSON.stringify({ code, verifier })
|
|
842
|
-
});
|
|
843
|
-
if (!response.ok) throw new Error(await problemDetail(response));
|
|
844
|
-
const body = await response.json();
|
|
845
|
-
return { token: body.token, name: body.name, email: body.email };
|
|
846
|
-
}
|
|
847
914
|
function failure(text) {
|
|
848
915
|
return { text, json: { error: text }, code: 1 };
|
|
849
916
|
}
|
|
@@ -1191,7 +1258,7 @@ async function run(argv) {
|
|
|
1191
1258
|
const positional = argv.filter((argument) => !argument.startsWith("-"));
|
|
1192
1259
|
const [command, ...rest] = positional;
|
|
1193
1260
|
if (argv.includes("--version")) {
|
|
1194
|
-
const { version } = await import("./package-
|
|
1261
|
+
const { version } = await import("./package-D-UHpX_a.js").then(
|
|
1195
1262
|
(module) => module.default
|
|
1196
1263
|
);
|
|
1197
1264
|
return { text: version, json: { version }, code: 0 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const name = "drop2run";
|
|
2
|
-
const version = "0.1
|
|
2
|
+
const version = "0.2.1";
|
|
3
3
|
const description = "Publish a static site to Drop2Run from the command line.";
|
|
4
4
|
const license = "MIT";
|
|
5
5
|
const type = "module";
|
|
@@ -8,7 +8,9 @@ const files = ["bin", "dist"];
|
|
|
8
8
|
const engines = { "node": ">=20" };
|
|
9
9
|
const publishConfig = { "access": "public" };
|
|
10
10
|
const homepage = "https://dropto.run";
|
|
11
|
-
const
|
|
11
|
+
const repository = { "type": "git", "url": "git+https://github.com/Drop-to-run/drop2run-cli.git" };
|
|
12
|
+
const bugs = { "url": "https://github.com/Drop-to-run/drop2run-cli/issues" };
|
|
13
|
+
const keywords = ["static-site", "deploy", "hosting", "cli", "ai", "claude", "agent", "llm"];
|
|
12
14
|
const scripts = { "prepublishOnly": "npm run build && npm test", "build": "vite build", "typecheck": "tsc --noEmit", "test": "vitest run", "lint": "biome check .", "format": "biome check --write ." };
|
|
13
15
|
const devDependencies = { "@biomejs/biome": "^2.5.11", "@types/node": "^26.4.0", "typescript": "^5.9.3", "vite": "^7.2.1", "vitest": "^4.1.11" };
|
|
14
16
|
const _package = {
|
|
@@ -22,12 +24,15 @@ const _package = {
|
|
|
22
24
|
engines,
|
|
23
25
|
publishConfig,
|
|
24
26
|
homepage,
|
|
27
|
+
repository,
|
|
28
|
+
bugs,
|
|
25
29
|
keywords,
|
|
26
30
|
scripts,
|
|
27
31
|
devDependencies
|
|
28
32
|
};
|
|
29
33
|
export {
|
|
30
34
|
bin,
|
|
35
|
+
bugs,
|
|
31
36
|
_package as default,
|
|
32
37
|
description,
|
|
33
38
|
devDependencies,
|
|
@@ -38,6 +43,7 @@ export {
|
|
|
38
43
|
license,
|
|
39
44
|
name,
|
|
40
45
|
publishConfig,
|
|
46
|
+
repository,
|
|
41
47
|
scripts,
|
|
42
48
|
type,
|
|
43
49
|
version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drop2run",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Publish a static site to Drop2Run from the command line.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,11 +18,22 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"homepage": "https://dropto.run",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Drop-to-run/drop2run-cli.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/Drop-to-run/drop2run-cli/issues"
|
|
27
|
+
},
|
|
21
28
|
"keywords": [
|
|
22
29
|
"static-site",
|
|
23
30
|
"deploy",
|
|
24
31
|
"hosting",
|
|
25
|
-
"cli"
|
|
32
|
+
"cli",
|
|
33
|
+
"ai",
|
|
34
|
+
"claude",
|
|
35
|
+
"agent",
|
|
36
|
+
"llm"
|
|
26
37
|
],
|
|
27
38
|
"scripts": {
|
|
28
39
|
"prepublishOnly": "npm run build && npm test",
|