@kittl/cli 0.0.1 → 0.0.3
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 +201 -0
- package/README.md +15 -105
- package/bin/dev.js +97 -0
- package/dist/chunk-2OOJ3TW4.js +143 -0
- package/dist/chunk-2OOJ3TW4.js.map +1 -0
- package/dist/chunk-3BPIJLS7.js +51 -0
- package/dist/chunk-3BPIJLS7.js.map +1 -0
- package/dist/chunk-EKU4DKQK.js +20 -0
- package/dist/chunk-EKU4DKQK.js.map +1 -0
- package/dist/{chunk-4ISWSLZ5.js → chunk-K4OFK7AL.js} +3 -3
- package/dist/chunk-K4OFK7AL.js.map +1 -0
- package/dist/chunk-SUQB7HN4.js +102 -0
- package/dist/chunk-SUQB7HN4.js.map +1 -0
- package/dist/{chunk-JGD3QFQS.js → chunk-YUKLWJFM.js} +326 -71
- package/dist/chunk-YUKLWJFM.js.map +1 -0
- package/dist/commands/app/init.js +726 -0
- package/dist/commands/app/init.js.map +1 -0
- package/dist/commands/app/release.js +125 -0
- package/dist/commands/app/release.js.map +1 -0
- package/dist/commands/app/update.js +45 -0
- package/dist/commands/app/update.js.map +1 -0
- package/dist/commands/app/upload.js +404 -5
- package/dist/commands/app/upload.js.map +1 -1
- package/dist/commands/auth/login.js +13 -68
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +1 -1
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/whoami.js +2 -2
- package/dist/commands/whoami.js +2 -2
- package/oclif.manifest.json +196 -0
- package/package.json +18 -27
- package/dist/chunk-4ISWSLZ5.js.map +0 -1
- package/dist/chunk-JGD3QFQS.js.map +0 -1
- package/dist/commands/app/create.js +0 -16
- package/dist/commands/app/create.js.map +0 -1
|
@@ -1,13 +1,412 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
formatExtensionArtifactUploadError,
|
|
3
|
+
isSystemError,
|
|
4
|
+
readInternalConfig
|
|
5
|
+
} from "../../chunk-2OOJ3TW4.js";
|
|
6
|
+
import {
|
|
7
|
+
useTerminalWidth
|
|
8
|
+
} from "../../chunk-EKU4DKQK.js";
|
|
9
|
+
import {
|
|
10
|
+
layoutStyles,
|
|
11
|
+
spacing,
|
|
12
|
+
textStyles
|
|
13
|
+
} from "../../chunk-3BPIJLS7.js";
|
|
14
|
+
import {
|
|
15
|
+
BaseCommand,
|
|
16
|
+
chunkArray,
|
|
17
|
+
contentTypeForPath,
|
|
18
|
+
listAllFilesUnderDir
|
|
19
|
+
} from "../../chunk-YUKLWJFM.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/app/upload.ts
|
|
22
|
+
import { stat } from "node:fs/promises";
|
|
23
|
+
import { resolve } from "node:path";
|
|
24
|
+
import { Flags } from "@oclif/core";
|
|
25
|
+
|
|
26
|
+
// src/core/extension-upload.core.ts
|
|
27
|
+
import { readFile } from "node:fs/promises";
|
|
28
|
+
import { basename, relative } from "node:path";
|
|
29
|
+
|
|
30
|
+
// src/services/extension-artifacts.service.ts
|
|
31
|
+
import axios from "axios";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
var uploadSignedUrlsResponseSchema = z.object({
|
|
34
|
+
success: z.literal(true),
|
|
35
|
+
results: z.array(
|
|
36
|
+
z.object({
|
|
37
|
+
url: z.string().min(1),
|
|
38
|
+
key: z.string(),
|
|
39
|
+
relativePath: z.string(),
|
|
40
|
+
contentType: z.string().min(1)
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
});
|
|
44
|
+
async function requestExtensionUploadSignedUrls(client, extensionId, files) {
|
|
45
|
+
const path = `/extensions/${extensionId}/versions/upload-signed-urls`;
|
|
46
|
+
const { data } = await client.post(path, { files });
|
|
47
|
+
const parsed = uploadSignedUrlsResponseSchema.safeParse(data);
|
|
48
|
+
if (!parsed.success) {
|
|
49
|
+
throw parsed.error;
|
|
50
|
+
}
|
|
51
|
+
const map = new Map(
|
|
52
|
+
parsed.data.results.map((r) => [r.relativePath, r])
|
|
53
|
+
);
|
|
54
|
+
const ordered = [];
|
|
55
|
+
for (const f of files) {
|
|
56
|
+
const row = map.get(f.relativePath);
|
|
57
|
+
if (row === void 0) {
|
|
58
|
+
throw new Error(`Missing presigned URL for path: ${f.relativePath}`);
|
|
59
|
+
}
|
|
60
|
+
ordered.push(row);
|
|
61
|
+
}
|
|
62
|
+
return ordered;
|
|
63
|
+
}
|
|
64
|
+
var s3Client = axios.create({
|
|
65
|
+
maxBodyLength: Infinity,
|
|
66
|
+
maxContentLength: Infinity,
|
|
67
|
+
timeout: 0
|
|
68
|
+
});
|
|
69
|
+
async function putPresignedUpload(url, body, contentType) {
|
|
70
|
+
await s3Client.put(url, body, {
|
|
71
|
+
headers: { "Content-Type": contentType }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/core/extension-upload.core.ts
|
|
76
|
+
var MAX_RELATIVE_PATH_LEN = 512;
|
|
77
|
+
var RELATIVE_PATH_SEGMENT = /^[a-zA-Z0-9._-]+$/;
|
|
78
|
+
var S3_PUT_MAX_ATTEMPTS = 3;
|
|
79
|
+
var S3_PUT_RETRY_BASE_MS = 500;
|
|
80
|
+
function normalizeDistPath(rawPath) {
|
|
81
|
+
return rawPath.replace(/\\/g, "/").trim().replace(/^(\.\/|\/)+/, "");
|
|
82
|
+
}
|
|
83
|
+
function validateUploadPath(relativePath) {
|
|
84
|
+
if (!relativePath) {
|
|
85
|
+
return "Path is empty.";
|
|
86
|
+
}
|
|
87
|
+
if (relativePath.includes("\\")) {
|
|
88
|
+
return "Path must use forward slashes only, not backslashes.";
|
|
89
|
+
}
|
|
90
|
+
if (relativePath.length > MAX_RELATIVE_PATH_LEN) {
|
|
91
|
+
return `Path exceeds max length (${MAX_RELATIVE_PATH_LEN}).`;
|
|
92
|
+
}
|
|
93
|
+
for (const segment of relativePath.split("/")) {
|
|
94
|
+
if (!segment || segment === "." || segment === "..") {
|
|
95
|
+
return `Invalid path segment "${segment}" (no empty, ".", or ".." segments).`;
|
|
96
|
+
}
|
|
97
|
+
if (!RELATIVE_PATH_SEGMENT.test(segment)) {
|
|
98
|
+
return `Invalid path segment "${segment}" (allowed: letters, digits, ".", "_", "-").`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
async function collectDistUploadDescriptors(distRoot) {
|
|
104
|
+
const files = await listAllFilesUnderDir(distRoot);
|
|
105
|
+
if (files.length === 0) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`No files found under ${distRoot}. Build your app before uploading.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const descriptorByRelativePath = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const absolutePath of files) {
|
|
112
|
+
const relativePath = normalizeDistPath(relative(distRoot, absolutePath));
|
|
113
|
+
const validationError = validateUploadPath(relativePath);
|
|
114
|
+
if (validationError) {
|
|
115
|
+
throw new Error(`${basename(absolutePath)}: ${validationError}`);
|
|
116
|
+
}
|
|
117
|
+
if (descriptorByRelativePath.has(relativePath)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Duplicate upload path after normalization: ${relativePath}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
descriptorByRelativePath.set(relativePath, {
|
|
123
|
+
absolutePath,
|
|
124
|
+
relativePath,
|
|
125
|
+
contentType: contentTypeForPath(absolutePath)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return [...descriptorByRelativePath.values()].sort(
|
|
129
|
+
(a, b) => a.relativePath.localeCompare(b.relativePath)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
async function putPresignedUploadWithRetry(url, body, contentType) {
|
|
133
|
+
let lastError;
|
|
134
|
+
for (let attempt = 1; attempt <= S3_PUT_MAX_ATTEMPTS; attempt++) {
|
|
135
|
+
try {
|
|
136
|
+
await putPresignedUpload(url, body, contentType);
|
|
137
|
+
return;
|
|
138
|
+
} catch (err) {
|
|
139
|
+
lastError = err;
|
|
140
|
+
if (attempt >= S3_PUT_MAX_ATTEMPTS) {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
await new Promise((r) => setTimeout(r, S3_PUT_RETRY_BASE_MS * attempt));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw lastError;
|
|
147
|
+
}
|
|
148
|
+
async function runArtifactUpload(client, extensionId, batches, opts) {
|
|
149
|
+
const { total, onProgress } = opts;
|
|
150
|
+
let uploaded = 0;
|
|
151
|
+
let lastRelativePath = "";
|
|
152
|
+
const uploadedRelativePaths = [];
|
|
153
|
+
for (let b = 0; b < batches.length; b++) {
|
|
154
|
+
const batch = batches[b];
|
|
155
|
+
if (!batch?.length) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const specs = batch.map((d) => ({
|
|
159
|
+
relativePath: d.relativePath,
|
|
160
|
+
contentType: d.contentType
|
|
161
|
+
}));
|
|
162
|
+
let signed;
|
|
163
|
+
try {
|
|
164
|
+
signed = await requestExtensionUploadSignedUrls(
|
|
165
|
+
client,
|
|
166
|
+
extensionId,
|
|
167
|
+
specs
|
|
168
|
+
);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return { ok: false, message: formatExtensionArtifactUploadError(e) };
|
|
171
|
+
}
|
|
172
|
+
for (let i = 0; i < batch.length; i++) {
|
|
173
|
+
const d = batch[i];
|
|
174
|
+
const row = signed[i];
|
|
175
|
+
if (!d || !row) {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
message: `Internal error: missing batch entry at index ${i} (batch ${b + 1}).`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (row.relativePath !== d.relativePath) {
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
failedPath: d.relativePath,
|
|
185
|
+
message: `Presigned URL order mismatch for ${d.relativePath} (batch ${b + 1}).`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
lastRelativePath = d.relativePath;
|
|
189
|
+
onProgress({
|
|
190
|
+
completedSoFar: uploaded,
|
|
191
|
+
total,
|
|
192
|
+
activeRelativePath: d.relativePath,
|
|
193
|
+
uploadedRelativePaths: [...uploadedRelativePaths]
|
|
194
|
+
});
|
|
195
|
+
let body;
|
|
196
|
+
try {
|
|
197
|
+
body = await readFile(d.absolutePath);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return {
|
|
200
|
+
ok: false,
|
|
201
|
+
failedPath: d.relativePath,
|
|
202
|
+
message: e instanceof Error ? `Failed to read ${d.relativePath}: ${e.message}` : String(e)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
await putPresignedUploadWithRetry(row.url, body, row.contentType);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
failedPath: d.relativePath,
|
|
211
|
+
message: e instanceof Error ? `S3 upload failed for ${d.relativePath}: ${e.message}` : `S3 upload failed for ${d.relativePath}: ${String(e)}`
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
uploaded++;
|
|
215
|
+
uploadedRelativePaths.push(d.relativePath);
|
|
216
|
+
onProgress({
|
|
217
|
+
completedSoFar: uploaded,
|
|
218
|
+
total,
|
|
219
|
+
activeRelativePath: d.relativePath,
|
|
220
|
+
uploadedRelativePaths: [...uploadedRelativePaths]
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
onProgress({
|
|
225
|
+
completedSoFar: uploaded,
|
|
226
|
+
total,
|
|
227
|
+
activeRelativePath: total <= 1 ? lastRelativePath : `${uploaded} files uploaded`,
|
|
228
|
+
uploadedRelativePaths: [...uploadedRelativePaths]
|
|
229
|
+
});
|
|
230
|
+
return { ok: true, uploaded };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/ui/views/app-upload/AppUploadProgressView.tsx
|
|
234
|
+
import { Box, Text } from "ink";
|
|
235
|
+
import { useEffect, useRef, useState } from "react";
|
|
236
|
+
|
|
237
|
+
// src/ui/views/app-upload/verbose-upload-paths.ts
|
|
238
|
+
var VERBOSE_UPLOAD_PATH_TAIL_MAX = 300;
|
|
239
|
+
function formatVerboseUploadPaths(paths, maxTailLines = VERBOSE_UPLOAD_PATH_TAIL_MAX) {
|
|
240
|
+
if (paths.length === 0) {
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
243
|
+
if (paths.length <= maxTailLines) {
|
|
244
|
+
return paths.join("\n");
|
|
245
|
+
}
|
|
246
|
+
const omitted = paths.length - maxTailLines;
|
|
247
|
+
return `\u2026 ${omitted} earlier path(s) omitted
|
|
248
|
+
${paths.slice(-maxTailLines).join("\n")}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/ui/views/app-upload/AppUploadProgressView.tsx
|
|
252
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
253
|
+
function ProgressBar({
|
|
254
|
+
done,
|
|
255
|
+
total,
|
|
256
|
+
width
|
|
257
|
+
}) {
|
|
258
|
+
const label = `${done}/${total}`;
|
|
259
|
+
const reserved = label.length + 2;
|
|
260
|
+
const barWidth = Math.max(2, width - reserved);
|
|
261
|
+
const inner = Math.min(barWidth, 40);
|
|
262
|
+
const filled = total === 0 ? 0 : Math.min(inner, Math.round(done / total * inner));
|
|
263
|
+
const bar = `${"\u2588".repeat(filled)}${"\u2591".repeat(inner - filled)}`;
|
|
264
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
265
|
+
/* @__PURE__ */ jsx(Text, { ...textStyles.muted, children: bar }),
|
|
266
|
+
" ",
|
|
267
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: label })
|
|
268
|
+
] });
|
|
269
|
+
}
|
|
270
|
+
function AppUploadProgressView({
|
|
271
|
+
client,
|
|
272
|
+
extensionId,
|
|
273
|
+
batches,
|
|
274
|
+
total,
|
|
275
|
+
verbose = false,
|
|
276
|
+
onDone
|
|
277
|
+
}) {
|
|
278
|
+
const termWidth = useTerminalWidth();
|
|
279
|
+
const layoutWidth = termWidth > 0 ? termWidth : "100%";
|
|
280
|
+
const progressBarWidth = termWidth > 0 ? termWidth : 80;
|
|
281
|
+
const [progress, setProgress] = useState(null);
|
|
282
|
+
const settledRef = useRef(false);
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
settledRef.current = false;
|
|
285
|
+
let cancelled = false;
|
|
286
|
+
let finishTimer;
|
|
287
|
+
void runArtifactUpload(client, extensionId, batches, {
|
|
288
|
+
total,
|
|
289
|
+
onProgress: (p) => {
|
|
290
|
+
if (!cancelled) {
|
|
291
|
+
setProgress(p);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}).then((result) => {
|
|
295
|
+
if (cancelled) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!result.ok) {
|
|
299
|
+
if (cancelled || settledRef.current) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
settledRef.current = true;
|
|
303
|
+
onDone({
|
|
304
|
+
kind: "error",
|
|
305
|
+
message: result.message,
|
|
306
|
+
failedPath: result.failedPath
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
setProgress(
|
|
311
|
+
(prev) => prev ? { ...prev, completedSoFar: result.uploaded, total } : prev
|
|
312
|
+
);
|
|
313
|
+
finishTimer = setTimeout(() => {
|
|
314
|
+
if (cancelled || settledRef.current) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
settledRef.current = true;
|
|
318
|
+
onDone({ kind: "success", uploaded: result.uploaded });
|
|
319
|
+
}, 0);
|
|
320
|
+
});
|
|
321
|
+
return () => {
|
|
322
|
+
cancelled = true;
|
|
323
|
+
if (finishTimer !== void 0) {
|
|
324
|
+
clearTimeout(finishTimer);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}, [batches, client, extensionId, onDone, total]);
|
|
328
|
+
const done = progress?.completedSoFar ?? 0;
|
|
329
|
+
const active = progress?.activeRelativePath ?? "\u2026";
|
|
330
|
+
const uploadedPaths = progress?.uploadedRelativePaths ?? [];
|
|
331
|
+
const showVerboseFileList = verbose && uploadedPaths.length > 0;
|
|
332
|
+
const verboseText = formatVerboseUploadPaths(uploadedPaths);
|
|
333
|
+
return /* @__PURE__ */ jsxs(Box, { width: layoutWidth, minWidth: 0, ...layoutStyles.viewColumn, children: [
|
|
334
|
+
/* @__PURE__ */ jsx(Box, { marginBottom: spacing.sm, children: /* @__PURE__ */ jsx(Text, { ...textStyles.title, children: "Upload extension artifacts" }) }),
|
|
335
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
336
|
+
/* @__PURE__ */ jsx(ProgressBar, { done, total, width: progressBarWidth }),
|
|
337
|
+
/* @__PURE__ */ jsx(Box, { marginTop: spacing.sm, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: active }) }),
|
|
338
|
+
showVerboseFileList ? /* @__PURE__ */ jsx(Box, { marginTop: spacing.xs, children: /* @__PURE__ */ jsx(Text, { ...textStyles.muted, wrap: "wrap", children: verboseText }) }) : null,
|
|
339
|
+
!progress ? /* @__PURE__ */ jsx(Box, { marginTop: spacing.sm, children: /* @__PURE__ */ jsx(Text, { ...textStyles.muted, children: "Preparing\u2026" }) }) : null
|
|
340
|
+
] })
|
|
341
|
+
] });
|
|
342
|
+
}
|
|
4
343
|
|
|
5
344
|
// src/commands/app/upload.ts
|
|
6
345
|
var AppUpload = class _AppUpload extends BaseCommand {
|
|
7
|
-
static description = "Upload
|
|
346
|
+
static description = "Upload build output to the extension draft.";
|
|
347
|
+
static flags = {
|
|
348
|
+
dist: Flags.string({
|
|
349
|
+
description: "Path to the build output directory (relative to cwd)",
|
|
350
|
+
default: "dist"
|
|
351
|
+
}),
|
|
352
|
+
verbose: Flags.boolean({
|
|
353
|
+
char: "v",
|
|
354
|
+
default: false,
|
|
355
|
+
description: "List each dist file path on its own line as uploads complete (no S3 keys)"
|
|
356
|
+
})
|
|
357
|
+
};
|
|
8
358
|
async run() {
|
|
9
|
-
await this.parse(_AppUpload);
|
|
10
|
-
this.
|
|
359
|
+
const { flags } = await this.parse(_AppUpload);
|
|
360
|
+
await this.ensureAuthenticated();
|
|
361
|
+
const cwd = process.cwd();
|
|
362
|
+
let extensionId;
|
|
363
|
+
try {
|
|
364
|
+
({ extensionId } = await readInternalConfig(cwd));
|
|
365
|
+
} catch (e) {
|
|
366
|
+
this.error(e instanceof Error ? e.message : String(e), { exit: 2 });
|
|
367
|
+
}
|
|
368
|
+
const distAbs = resolve(cwd, flags.dist);
|
|
369
|
+
try {
|
|
370
|
+
const st = await stat(distAbs);
|
|
371
|
+
if (!st.isDirectory()) {
|
|
372
|
+
this.error(`Not a directory: ${distAbs}`, { exit: 2 });
|
|
373
|
+
}
|
|
374
|
+
} catch (e) {
|
|
375
|
+
if (isSystemError(e, "ENOENT")) {
|
|
376
|
+
this.error(
|
|
377
|
+
`Dist directory not found: ${distAbs}. Build your app first, or pass --dist.`,
|
|
378
|
+
{ exit: 2 }
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (isSystemError(e, "EACCES") || isSystemError(e, "EPERM")) {
|
|
382
|
+
this.error(`Cannot access dist directory: ${distAbs}`, { exit: 2 });
|
|
383
|
+
}
|
|
384
|
+
if (isSystemError(e, "ENOTDIR")) {
|
|
385
|
+
this.error(`Invalid dist path (not a directory): ${distAbs}`, {
|
|
386
|
+
exit: 2
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
this.error(e instanceof Error ? e.message : String(e), { exit: 2 });
|
|
390
|
+
}
|
|
391
|
+
let descriptors;
|
|
392
|
+
try {
|
|
393
|
+
descriptors = await collectDistUploadDescriptors(distAbs);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
this.error(e instanceof Error ? e.message : String(e), { exit: 2 });
|
|
396
|
+
}
|
|
397
|
+
const result = await this.renderView(AppUploadProgressView, {
|
|
398
|
+
client: this.getKittlApiClient(),
|
|
399
|
+
extensionId,
|
|
400
|
+
batches: chunkArray(descriptors, 50),
|
|
401
|
+
total: descriptors.length,
|
|
402
|
+
verbose: flags.verbose
|
|
403
|
+
});
|
|
404
|
+
if (result.kind === "error") {
|
|
405
|
+
const msg = result.failedPath ? `${result.failedPath}
|
|
406
|
+
${result.message}` : result.message;
|
|
407
|
+
this.error(msg, { exit: 2 });
|
|
408
|
+
}
|
|
409
|
+
this.log(`Uploaded ${result.uploaded} file(s) to your extension draft.`);
|
|
11
410
|
}
|
|
12
411
|
};
|
|
13
412
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/app/upload.ts"],"sourcesContent":["import { BaseCommand } from '../../base-command';\n\nexport default class AppUpload extends BaseCommand {\n public static override description =\n 'Upload app files for review (coming soon)';\n\n public async run(): Promise<void> {\n await this.parse(AppUpload);\n\n this.log('Not implemented yet.');\n }\n}\n"],"mappings":";;;;;AAEA,IAAqB,YAArB,MAAqB,mBAAkB,YAAY;AAAA,EACjD,OAAuB,cACrB;AAAA,EAEF,MAAa,MAAqB;AAChC,UAAM,KAAK,MAAM,UAAS;AAE1B,SAAK,IAAI,sBAAsB;AAAA,EACjC;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/app/upload.ts","../../../src/core/extension-upload.core.ts","../../../src/services/extension-artifacts.service.ts","../../../src/ui/views/app-upload/AppUploadProgressView.tsx","../../../src/ui/views/app-upload/verbose-upload-paths.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { Flags } from '@oclif/core';\nimport { BaseCommand } from '../../core/core.command';\nimport { isSystemError } from '../../core/error';\nimport {\n collectDistUploadDescriptors,\n type DistUploadFileDescriptor,\n} from '../../core/extension-upload.core';\nimport { readInternalConfig } from '../../core/internal.config';\nimport { chunkArray } from '../../core/utils';\nimport { AppUploadProgressView } from '../../ui/views/app-upload';\n\nexport default class AppUpload extends BaseCommand {\n public static override description =\n 'Upload build output to the extension draft.';\n\n public static override flags = {\n dist: Flags.string({\n description: 'Path to the build output directory (relative to cwd)',\n default: 'dist',\n }),\n verbose: Flags.boolean({\n char: 'v',\n default: false,\n description:\n 'List each dist file path on its own line as uploads complete (no S3 keys)',\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(AppUpload);\n\n await this.ensureAuthenticated();\n\n const cwd = process.cwd();\n\n let extensionId: string;\n try {\n ({ extensionId } = await readInternalConfig(cwd));\n } catch (e) {\n this.error(e instanceof Error ? e.message : String(e), { exit: 2 });\n }\n\n const distAbs = resolve(cwd, flags.dist);\n try {\n const st = await stat(distAbs);\n if (!st.isDirectory()) {\n this.error(`Not a directory: ${distAbs}`, { exit: 2 });\n }\n } catch (e) {\n if (isSystemError(e, 'ENOENT')) {\n this.error(\n `Dist directory not found: ${distAbs}. Build your app first, or pass --dist.`,\n { exit: 2 },\n );\n }\n if (isSystemError(e, 'EACCES') || isSystemError(e, 'EPERM')) {\n this.error(`Cannot access dist directory: ${distAbs}`, { exit: 2 });\n }\n if (isSystemError(e, 'ENOTDIR')) {\n this.error(`Invalid dist path (not a directory): ${distAbs}`, {\n exit: 2,\n });\n }\n this.error(e instanceof Error ? e.message : String(e), { exit: 2 });\n }\n\n let descriptors: DistUploadFileDescriptor[];\n try {\n descriptors = await collectDistUploadDescriptors(distAbs);\n } catch (e) {\n this.error(e instanceof Error ? e.message : String(e), { exit: 2 });\n }\n\n const result = await this.renderView(AppUploadProgressView, {\n client: this.getKittlApiClient(),\n extensionId,\n batches: chunkArray(descriptors, 50),\n total: descriptors.length,\n verbose: flags.verbose,\n });\n\n if (result.kind === 'error') {\n const msg = result.failedPath\n ? `${result.failedPath}\\n${result.message}`\n : result.message;\n this.error(msg, { exit: 2 });\n }\n\n this.log(`Uploaded ${result.uploaded} file(s) to your extension draft.`);\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { basename, relative } from 'node:path';\nimport type { AxiosInstance } from 'axios';\nimport {\n type ExtensionUploadSignedUrlRow,\n putPresignedUpload,\n requestExtensionUploadSignedUrls,\n} from '../services/extension-artifacts.service';\nimport { formatExtensionArtifactUploadError } from './error';\nimport { contentTypeForPath, listAllFilesUnderDir } from './files';\n\nconst MAX_RELATIVE_PATH_LEN = 512;\nconst RELATIVE_PATH_SEGMENT = /^[a-zA-Z0-9._-]+$/;\nconst S3_PUT_MAX_ATTEMPTS = 3;\nconst S3_PUT_RETRY_BASE_MS = 500;\n\nexport type DistUploadFileDescriptor = {\n absolutePath: string;\n relativePath: string;\n contentType: string;\n};\n\nexport type ArtifactUploadProgress = {\n completedSoFar: number;\n total: number;\n // path currently being read or uploaded.\n activeRelativePath: string;\n // paths uploaded so far (same order as uploads).\n uploadedRelativePaths: string[];\n};\n\nexport type ArtifactUploadResult =\n | { ok: true; uploaded: number }\n | { ok: false; message: string; failedPath?: string };\n\n// --- Path + MIME (upload-signed-urls contract) ---------------------------------------------------\n\n/** POSIX slashes, trim, strip leading `./` and `/` (single pass after `path.relative`). */\nexport function normalizeDistPath(rawPath: string): string {\n return rawPath\n .replace(/\\\\/g, '/')\n .trim()\n .replace(/^(\\.\\/|\\/)+/, '');\n}\n\n/** API path validation (segments, length, traversal). */\nexport function validateUploadPath(relativePath: string): string | null {\n if (!relativePath) {\n return 'Path is empty.';\n }\n if (relativePath.includes('\\\\')) {\n return 'Path must use forward slashes only, not backslashes.';\n }\n if (relativePath.length > MAX_RELATIVE_PATH_LEN) {\n return `Path exceeds max length (${MAX_RELATIVE_PATH_LEN}).`;\n }\n for (const segment of relativePath.split('/')) {\n if (!segment || segment === '.' || segment === '..') {\n return `Invalid path segment \"${segment}\" (no empty, \".\", or \"..\" segments).`;\n }\n if (!RELATIVE_PATH_SEGMENT.test(segment)) {\n return `Invalid path segment \"${segment}\" (allowed: letters, digits, \".\", \"_\", \"-\").`;\n }\n }\n return null;\n}\n\n/**\n * Lists `distRoot` recursively, validates paths, dedupes, sorts for stable progress / API order.\n */\nexport async function collectDistUploadDescriptors(\n distRoot: string,\n): Promise<DistUploadFileDescriptor[]> {\n const files = await listAllFilesUnderDir(distRoot);\n\n if (files.length === 0) {\n throw new Error(\n `No files found under ${distRoot}. Build your app before uploading.`,\n );\n }\n\n const descriptorByRelativePath = new Map<string, DistUploadFileDescriptor>();\n\n for (const absolutePath of files) {\n const relativePath = normalizeDistPath(relative(distRoot, absolutePath));\n const validationError = validateUploadPath(relativePath);\n if (validationError) {\n throw new Error(`${basename(absolutePath)}: ${validationError}`);\n }\n if (descriptorByRelativePath.has(relativePath)) {\n throw new Error(\n `Duplicate upload path after normalization: ${relativePath}`,\n );\n }\n descriptorByRelativePath.set(relativePath, {\n absolutePath,\n relativePath,\n contentType: contentTypeForPath(absolutePath),\n });\n }\n\n return [...descriptorByRelativePath.values()].sort((a, b) =>\n a.relativePath.localeCompare(b.relativePath),\n );\n}\n\nasync function putPresignedUploadWithRetry(\n url: string,\n body: Buffer,\n contentType: string,\n): Promise<void> {\n let lastError: unknown;\n for (let attempt = 1; attempt <= S3_PUT_MAX_ATTEMPTS; attempt++) {\n try {\n await putPresignedUpload(url, body, contentType);\n return;\n } catch (err) {\n lastError = err;\n if (attempt >= S3_PUT_MAX_ATTEMPTS) {\n break;\n }\n await new Promise((r) => setTimeout(r, S3_PUT_RETRY_BASE_MS * attempt));\n }\n }\n throw lastError;\n}\n\n// --- Presigned batch upload ----------------------------------------------------------------------\n\n/**\n * Presigned URL batching + S3 PUTs with progress callbacks for Ink UI.\n * S3 PUTs retry with linear backoff.\n */\nexport async function runArtifactUpload(\n client: AxiosInstance,\n extensionId: string,\n batches: DistUploadFileDescriptor[][],\n opts: {\n total: number;\n onProgress: (p: ArtifactUploadProgress) => void;\n },\n): Promise<ArtifactUploadResult> {\n const { total, onProgress } = opts;\n let uploaded = 0;\n let lastRelativePath = '';\n const uploadedRelativePaths: string[] = [];\n\n for (let b = 0; b < batches.length; b++) {\n const batch = batches[b];\n if (!batch?.length) {\n continue;\n }\n const specs = batch.map((d) => ({\n relativePath: d.relativePath,\n contentType: d.contentType,\n }));\n let signed: ExtensionUploadSignedUrlRow[];\n try {\n signed = await requestExtensionUploadSignedUrls(\n client,\n extensionId,\n specs,\n );\n } catch (e) {\n return { ok: false, message: formatExtensionArtifactUploadError(e) };\n }\n\n for (let i = 0; i < batch.length; i++) {\n const d = batch[i];\n const row = signed[i];\n if (!d || !row) {\n return {\n ok: false,\n message: `Internal error: missing batch entry at index ${i} (batch ${b + 1}).`,\n };\n }\n if (row.relativePath !== d.relativePath) {\n return {\n ok: false,\n failedPath: d.relativePath,\n message: `Presigned URL order mismatch for ${d.relativePath} (batch ${b + 1}).`,\n };\n }\n\n lastRelativePath = d.relativePath;\n\n onProgress({\n completedSoFar: uploaded,\n total,\n activeRelativePath: d.relativePath,\n uploadedRelativePaths: [...uploadedRelativePaths],\n });\n\n let body: Buffer;\n try {\n body = await readFile(d.absolutePath);\n } catch (e) {\n return {\n ok: false,\n failedPath: d.relativePath,\n message:\n e instanceof Error\n ? `Failed to read ${d.relativePath}: ${e.message}`\n : String(e),\n };\n }\n\n try {\n await putPresignedUploadWithRetry(row.url, body, row.contentType);\n } catch (e) {\n return {\n ok: false,\n failedPath: d.relativePath,\n message:\n e instanceof Error\n ? `S3 upload failed for ${d.relativePath}: ${e.message}`\n : `S3 upload failed for ${d.relativePath}: ${String(e)}`,\n };\n }\n\n uploaded++;\n uploadedRelativePaths.push(d.relativePath);\n onProgress({\n completedSoFar: uploaded,\n total,\n activeRelativePath: d.relativePath,\n uploadedRelativePaths: [...uploadedRelativePaths],\n });\n }\n }\n\n onProgress({\n completedSoFar: uploaded,\n total,\n activeRelativePath:\n total <= 1 ? lastRelativePath : `${uploaded} files uploaded`,\n uploadedRelativePaths: [...uploadedRelativePaths],\n });\n return { ok: true, uploaded };\n}\n","import type { AxiosInstance } from 'axios';\nimport axios from 'axios';\nimport { z } from 'zod';\n\nconst uploadSignedUrlsResponseSchema = z.object({\n success: z.literal(true),\n results: z.array(\n z.object({\n url: z.string().min(1),\n key: z.string(),\n relativePath: z.string(),\n contentType: z.string().min(1),\n }),\n ),\n});\n\nexport type UploadSignedUrlFileSpec = {\n relativePath: string;\n contentType: string;\n};\n\nexport type ExtensionUploadSignedUrlRow = {\n url: string;\n key: string;\n relativePath: string;\n contentType: string;\n};\n\nexport async function requestExtensionUploadSignedUrls(\n client: AxiosInstance,\n extensionId: string,\n files: UploadSignedUrlFileSpec[],\n): Promise<ExtensionUploadSignedUrlRow[]> {\n const path = `/extensions/${extensionId}/versions/upload-signed-urls`;\n const { data } = await client.post(path, { files });\n const parsed = uploadSignedUrlsResponseSchema.safeParse(data);\n if (!parsed.success) {\n throw parsed.error;\n }\n const map = new Map(\n parsed.data.results.map((r) => [r.relativePath, r] as const),\n );\n const ordered: ExtensionUploadSignedUrlRow[] = [];\n for (const f of files) {\n const row = map.get(f.relativePath);\n if (row === undefined) {\n throw new Error(`Missing presigned URL for path: ${f.relativePath}`);\n }\n ordered.push(row);\n }\n return ordered;\n}\n\n/**\n * Dedicated Axios instance for presigned S3 PUT requests only.\n * `maxBodyLength` / `maxContentLength`: avoid Axios body-size caps on large assets.\n * `timeout: 0`: no Axios deadline on slow uploads (TCP / network still apply).\n */\nconst s3Client = axios.create({\n maxBodyLength: Infinity,\n maxContentLength: Infinity,\n timeout: 0,\n});\n\n/**\n * PUT raw bytes to a presigned URL using {@link s3Client} only.\n * `contentType` must match the value the API used when signing, byte-for-byte, or S3 returns 403.\n */\nexport async function putPresignedUpload(\n url: string,\n body: Buffer,\n contentType: string,\n): Promise<void> {\n await s3Client.put(url, body, {\n headers: { 'Content-Type': contentType },\n });\n}\n","import type { AxiosInstance } from 'axios';\nimport { Box, Text } from 'ink';\nimport { useEffect, useRef, useState } from 'react';\nimport {\n type ArtifactUploadProgress,\n type DistUploadFileDescriptor,\n runArtifactUpload,\n} from '../../../core/extension-upload.core';\nimport { useTerminalWidth } from '../../hooks';\nimport { layoutStyles, textStyles } from '../../theme/styles';\nimport { spacing } from '../../theme/tokens';\nimport { formatVerboseUploadPaths } from './verbose-upload-paths';\n\nexport type AppUploadProgressResult =\n | { kind: 'success'; uploaded: number }\n | { kind: 'error'; message: string; failedPath?: string };\n\nexport type AppUploadProgressViewProps = {\n client: AxiosInstance;\n extensionId: string;\n batches: DistUploadFileDescriptor[][];\n total: number;\n // When true, completed dist paths are listed one per line\n verbose?: boolean;\n onDone: (result: AppUploadProgressResult) => void;\n};\n\nfunction ProgressBar({\n done,\n total,\n width,\n}: {\n done: number;\n total: number;\n width: number;\n}) {\n const label = `${done}/${total}`;\n const reserved = label.length + 2;\n const barWidth = Math.max(2, width - reserved);\n const inner = Math.min(barWidth, 40);\n const filled =\n total === 0 ? 0 : Math.min(inner, Math.round((done / total) * inner));\n const bar = `${'█'.repeat(filled)}${'░'.repeat(inner - filled)}`;\n return (\n <Text>\n <Text {...textStyles.muted}>{bar}</Text> <Text bold>{label}</Text>\n </Text>\n );\n}\n\nexport function AppUploadProgressView({\n client,\n extensionId,\n batches,\n total,\n verbose = false,\n onDone,\n}: AppUploadProgressViewProps) {\n const termWidth = useTerminalWidth();\n const layoutWidth = termWidth > 0 ? termWidth : '100%';\n const progressBarWidth = termWidth > 0 ? termWidth : 80;\n const [progress, setProgress] = useState<ArtifactUploadProgress | null>(null);\n const settledRef = useRef(false);\n\n useEffect(() => {\n settledRef.current = false;\n let cancelled = false;\n let finishTimer: ReturnType<typeof setTimeout> | undefined;\n\n void runArtifactUpload(client, extensionId, batches, {\n total,\n onProgress: (p) => {\n if (!cancelled) {\n setProgress(p);\n }\n },\n }).then((result) => {\n if (cancelled) {\n return;\n }\n if (!result.ok) {\n if (cancelled || settledRef.current) {\n return;\n }\n settledRef.current = true;\n onDone({\n kind: 'error',\n message: result.message,\n failedPath: result.failedPath,\n });\n return;\n }\n setProgress((prev) =>\n prev ? { ...prev, completedSoFar: result.uploaded, total } : prev,\n );\n finishTimer = setTimeout(() => {\n if (cancelled || settledRef.current) {\n return;\n }\n settledRef.current = true;\n onDone({ kind: 'success', uploaded: result.uploaded });\n }, 0);\n });\n\n return () => {\n cancelled = true;\n if (finishTimer !== undefined) {\n clearTimeout(finishTimer);\n }\n };\n }, [batches, client, extensionId, onDone, total]);\n\n const done = progress?.completedSoFar ?? 0;\n const active = progress?.activeRelativePath ?? '…';\n const uploadedPaths = progress?.uploadedRelativePaths ?? [];\n const showVerboseFileList = verbose && uploadedPaths.length > 0;\n const verboseText = formatVerboseUploadPaths(uploadedPaths);\n\n return (\n <Box width={layoutWidth} minWidth={0} {...layoutStyles.viewColumn}>\n <Box marginBottom={spacing.sm}>\n <Text {...textStyles.title}>Upload extension artifacts</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <ProgressBar done={done} total={total} width={progressBarWidth} />\n <Box marginTop={spacing.sm}>\n <Text bold color=\"white\">\n {active}\n </Text>\n </Box>\n {showVerboseFileList ? (\n <Box marginTop={spacing.xs}>\n <Text {...textStyles.muted} wrap=\"wrap\">\n {verboseText}\n </Text>\n </Box>\n ) : null}\n {!progress ? (\n <Box marginTop={spacing.sm}>\n <Text {...textStyles.muted}>Preparing…</Text>\n </Box>\n ) : null}\n </Box>\n </Box>\n );\n}\n","/** Tail cap for verbose CLI output (one multiline Text, not N Ink nodes). */\nexport const VERBOSE_UPLOAD_PATH_TAIL_MAX = 300;\n\n/**\n * Renders completed dist paths as newline-separated text; when there are more\n * than `maxTailLines`, keeps only the last `maxTailLines` and prefixes a\n * one-line omission summary.\n */\nexport function formatVerboseUploadPaths(\n paths: string[],\n maxTailLines = VERBOSE_UPLOAD_PATH_TAIL_MAX,\n): string {\n if (paths.length === 0) {\n return '';\n }\n if (paths.length <= maxTailLines) {\n return paths.join('\\n');\n }\n const omitted = paths.length - maxTailLines;\n return `… ${omitted} earlier path(s) omitted\\n${paths.slice(-maxTailLines).join('\\n')}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,62 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useTerminalWidth
|
|
3
|
+
} from "../../chunk-EKU4DKQK.js";
|
|
4
|
+
import {
|
|
5
|
+
layoutStyles,
|
|
6
|
+
textStyles
|
|
7
|
+
} from "../../chunk-3BPIJLS7.js";
|
|
1
8
|
import {
|
|
2
9
|
BaseCommand,
|
|
3
10
|
INK_VIEW_UNMOUNT_REASON,
|
|
4
11
|
authService
|
|
5
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-YUKLWJFM.js";
|
|
6
13
|
|
|
7
|
-
// src/ui/views/LoginView.tsx
|
|
14
|
+
// src/ui/views/login/LoginView.tsx
|
|
8
15
|
import { Box, Text as Text2 } from "ink";
|
|
9
|
-
import { useCallback, useEffect as
|
|
16
|
+
import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
10
17
|
|
|
11
18
|
// src/ui/components/InlineSpinner.tsx
|
|
12
19
|
import { Text } from "ink";
|
|
13
20
|
import { useEffect, useState } from "react";
|
|
14
|
-
|
|
15
|
-
// src/ui/theme/tokens.ts
|
|
16
|
-
var colors = {
|
|
17
|
-
textPrimary: "white",
|
|
18
|
-
textMuted: "gray",
|
|
19
|
-
success: "green",
|
|
20
|
-
warning: "yellow",
|
|
21
|
-
danger: "red",
|
|
22
|
-
accent: "cyan"
|
|
23
|
-
};
|
|
24
|
-
var spacing = {
|
|
25
|
-
xs: 0,
|
|
26
|
-
sm: 1,
|
|
27
|
-
md: 2
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// src/ui/theme/styles.ts
|
|
31
|
-
var layoutStyles = {
|
|
32
|
-
viewColumn: {
|
|
33
|
-
flexDirection: "column",
|
|
34
|
-
padding: spacing.sm
|
|
35
|
-
},
|
|
36
|
-
section: {
|
|
37
|
-
marginTop: spacing.sm
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
var textStyles = {
|
|
41
|
-
title: {
|
|
42
|
-
bold: true,
|
|
43
|
-
color: colors.accent
|
|
44
|
-
},
|
|
45
|
-
muted: {
|
|
46
|
-
color: colors.textMuted
|
|
47
|
-
},
|
|
48
|
-
success: {
|
|
49
|
-
color: colors.success
|
|
50
|
-
},
|
|
51
|
-
warning: {
|
|
52
|
-
color: colors.warning
|
|
53
|
-
},
|
|
54
|
-
error: {
|
|
55
|
-
color: colors.danger
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// src/ui/components/InlineSpinner.tsx
|
|
60
21
|
import { jsxs } from "react/jsx-runtime";
|
|
61
22
|
var frames = ["-", "\\", "|", "/"];
|
|
62
23
|
function InlineSpinner({ label }) {
|
|
@@ -74,30 +35,14 @@ function InlineSpinner({ label }) {
|
|
|
74
35
|
] });
|
|
75
36
|
}
|
|
76
37
|
|
|
77
|
-
// src/ui/
|
|
78
|
-
import { useStdout } from "ink";
|
|
79
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
80
|
-
function useTerminalWidth() {
|
|
81
|
-
const { stdout } = useStdout();
|
|
82
|
-
const [width, setWidth] = useState2(() => stdout.columns ?? 80);
|
|
83
|
-
useEffect2(() => {
|
|
84
|
-
const onResize = () => setWidth(stdout.columns ?? 80);
|
|
85
|
-
stdout.on("resize", onResize);
|
|
86
|
-
return () => {
|
|
87
|
-
stdout.off("resize", onResize);
|
|
88
|
-
};
|
|
89
|
-
}, [stdout]);
|
|
90
|
-
return width;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/ui/views/LoginView.tsx
|
|
38
|
+
// src/ui/views/login/LoginView.tsx
|
|
94
39
|
import { jsx, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
95
40
|
var CHECKING_SESSION = "Checking session\u2026";
|
|
96
41
|
var SIGNING_IN = "Signing in\u2026";
|
|
97
42
|
function LoginView({ onDone }) {
|
|
98
43
|
const width = useTerminalWidth();
|
|
99
44
|
const doneRef = useRef(false);
|
|
100
|
-
const [phase, setPhase] =
|
|
45
|
+
const [phase, setPhase] = useState2("checking");
|
|
101
46
|
const finish = useCallback(
|
|
102
47
|
(result, force = false) => {
|
|
103
48
|
if (doneRef.current && !force)
|
|
@@ -107,7 +52,7 @@ function LoginView({ onDone }) {
|
|
|
107
52
|
},
|
|
108
53
|
[onDone]
|
|
109
54
|
);
|
|
110
|
-
|
|
55
|
+
useEffect2(() => {
|
|
111
56
|
doneRef.current = false;
|
|
112
57
|
const ac = new AbortController();
|
|
113
58
|
void (async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/views/LoginView.tsx","../../../src/ui/components/InlineSpinner.tsx","../../../src/
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/views/login/LoginView.tsx","../../../src/ui/components/InlineSpinner.tsx","../../../src/commands/auth/login.ts"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { INK_VIEW_UNMOUNT_REASON } from '../../../core/utils';\nimport { authService } from '../../../services/auth.service';\nimport { InlineSpinner } from '../../components/InlineSpinner';\nimport { useTerminalWidth } from '../../hooks';\nimport { layoutStyles, textStyles } from '../../theme/styles';\n\nconst CHECKING_SESSION = 'Checking session…';\nconst SIGNING_IN = 'Signing in…';\n\nexport type LoginResult =\n | { kind: 'success'; reusedSession?: boolean }\n | { kind: 'cancelled' }\n | { kind: 'error'; error: Error };\n\nexport type LoginViewProps = {\n onDone: (result: LoginResult) => void;\n};\n\n/**\n * OAuth login TUI.\n * **Unmount:** aborts the local `AbortController` so discovery + the callback server stop cleanly.\n */\nexport function LoginView({ onDone }: LoginViewProps) {\n const width = useTerminalWidth();\n const doneRef = useRef(false);\n const [phase, setPhase] = useState<'checking' | 'signing-in'>('checking');\n\n const finish = useCallback(\n (result: LoginResult, force = false) => {\n if (doneRef.current && !force) return;\n doneRef.current = true;\n onDone(result);\n },\n [onDone],\n );\n\n useEffect(() => {\n doneRef.current = false;\n const ac = new AbortController();\n\n void (async () => {\n try {\n const existing = await authService.getSession();\n if (ac.signal.aborted) {\n finish({ kind: 'cancelled' }, true);\n return;\n }\n if (existing?.accessToken) {\n finish({ kind: 'success', reusedSession: true });\n return;\n }\n\n setPhase('signing-in');\n await authService.login({ signal: ac.signal });\n finish({ kind: 'success' });\n } catch (error) {\n if (error === INK_VIEW_UNMOUNT_REASON) {\n finish({ kind: 'cancelled' }, true);\n return;\n }\n if (doneRef.current) return;\n finish({\n kind: 'error',\n error: error instanceof Error ? error : new Error('Auth failed'),\n });\n }\n })();\n\n return () => {\n doneRef.current = true;\n ac.abort(INK_VIEW_UNMOUNT_REASON);\n };\n }, [finish]);\n\n return (\n <Box\n {...layoutStyles.viewColumn}\n width={width > 0 ? width : '100%'}\n minWidth={0}\n >\n <Text {...textStyles.title}>Kittl CLI Authentication</Text>\n <Box {...layoutStyles.section}>\n <InlineSpinner\n label={phase === 'checking' ? CHECKING_SESSION : SIGNING_IN}\n />\n </Box>\n </Box>\n );\n}\n","import { Text } from 'ink';\nimport { useEffect, useState } from 'react';\nimport { textStyles } from '../theme/styles';\n\nexport type InlineSpinnerProps = {\n label: string;\n};\nconst frames = ['-', '\\\\', '|', '/'];\nexport function InlineSpinner({ label }: InlineSpinnerProps) {\n const [frameIndex, setFrameIndex] = useState(0);\n\n useEffect(() => {\n const timer = setInterval(() => {\n setFrameIndex((current) => (current + 1) % frames.length);\n }, 80);\n return () => clearInterval(timer);\n }, []);\n\n return (\n <Text {...textStyles.muted}>\n {frames[frameIndex]} {label}\n </Text>\n );\n}\n","import { BaseCommand } from '../../core/core.command';\nimport { LoginView } from '../../ui/views/login';\n\nexport default class AuthLogin extends BaseCommand {\n public static description = 'Authenticate with your Kittl account';\n\n public async run(): Promise<void> {\n await this.parse(AuthLogin);\n\n const result = await this.renderView(LoginView);\n\n if (result.kind === 'cancelled') {\n this.exit(130);\n }\n if (result.kind === 'error') {\n throw result.error;\n }\n\n if (result.reusedSession) {\n this.log('Already signed in.');\n } else {\n this.log('Signed in successfully.');\n }\n }\n}\n"],"mappings":"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","names":["Text","useEffect","useState","jsxs","useState","useEffect","Text"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/auth/logout.ts"],"sourcesContent":["import { BaseCommand } from '../../
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/auth/logout.ts"],"sourcesContent":["import { BaseCommand } from '../../core/core.command';\nimport { authService } from '../../services/auth.service';\n\nexport default class AuthLogout extends BaseCommand {\n public static override description = 'Clear local Kittl CLI session';\n\n public async run(): Promise<void> {\n await this.parse(AuthLogout);\n\n await authService.logout();\n this.log('Logged out.');\n }\n}\n"],"mappings":";;;;;;AAGA,IAAqB,aAArB,MAAqB,oBAAmB,YAAY;AAAA,EAClD,OAAuB,cAAc;AAAA,EAErC,MAAa,MAAqB;AAChC,UAAM,KAAK,MAAM,WAAU;AAE3B,UAAM,YAAY,OAAO;AACzB,SAAK,IAAI,aAAa;AAAA,EACxB;AACF;","names":[]}
|