dist-site 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/bin/dist.js +2 -0
- package/dist/index.js +584 -0
- package/package.json +36 -0
package/bin/dist.js
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
// src/commands/deploy.ts
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
|
|
4
|
+
// src/lib/config.ts
|
|
5
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
var CONFIG_DIR = join(homedir(), ".dist");
|
|
9
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
10
|
+
async function getConfig() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(CONFIG_FILE, "utf-8");
|
|
13
|
+
const config = JSON.parse(raw);
|
|
14
|
+
if (config.api_token) return config;
|
|
15
|
+
return null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function updateConfig(updates) {
|
|
21
|
+
let config = {};
|
|
22
|
+
try {
|
|
23
|
+
const raw = await readFile(CONFIG_FILE, "utf-8");
|
|
24
|
+
config = JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
config = { ...config, ...updates };
|
|
28
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
29
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
async function clearConfig() {
|
|
33
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
34
|
+
await writeFile(CONFIG_FILE, JSON.stringify({}, null, 2) + "\n");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/lib/ignore.ts
|
|
38
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
39
|
+
import { join as join2 } from "path";
|
|
40
|
+
import ignore from "ignore";
|
|
41
|
+
|
|
42
|
+
// ../shared/dist/constants.js
|
|
43
|
+
var FREE_TIER = {
|
|
44
|
+
deploys_per_day: 10,
|
|
45
|
+
deploys_per_month: 50,
|
|
46
|
+
max_deployment_size_bytes: 50 * 1024 * 1024,
|
|
47
|
+
// 50 MB
|
|
48
|
+
max_file_count: 5e3,
|
|
49
|
+
max_single_file_bytes: 25 * 1024 * 1024,
|
|
50
|
+
// 25 MB
|
|
51
|
+
ttl_seconds: 7 * 24 * 60 * 60,
|
|
52
|
+
// 7 days
|
|
53
|
+
max_concurrent_deployments: 20
|
|
54
|
+
};
|
|
55
|
+
var PAID_TIER = {
|
|
56
|
+
deploys_per_day: Infinity,
|
|
57
|
+
deploys_per_month: Infinity,
|
|
58
|
+
max_deployment_size_bytes: 200 * 1024 * 1024,
|
|
59
|
+
// 200 MB
|
|
60
|
+
max_file_count: 5e4,
|
|
61
|
+
max_single_file_bytes: 100 * 1024 * 1024,
|
|
62
|
+
// 100 MB
|
|
63
|
+
ttl_seconds: 30 * 24 * 60 * 60,
|
|
64
|
+
// 30 days
|
|
65
|
+
max_concurrent_deployments: Infinity
|
|
66
|
+
};
|
|
67
|
+
var API_BASE_URL = "https://api.dist.site";
|
|
68
|
+
var BUILT_IN_IGNORES = [
|
|
69
|
+
"node_modules",
|
|
70
|
+
".git",
|
|
71
|
+
".DS_Store",
|
|
72
|
+
"Thumbs.db",
|
|
73
|
+
".env",
|
|
74
|
+
".env.*",
|
|
75
|
+
"*.log",
|
|
76
|
+
".wrangler",
|
|
77
|
+
".cache",
|
|
78
|
+
".next",
|
|
79
|
+
".nuxt",
|
|
80
|
+
".svelte-kit"
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// src/lib/ignore.ts
|
|
84
|
+
async function createIgnoreFilter(dir) {
|
|
85
|
+
const ig = ignore();
|
|
86
|
+
ig.add(BUILT_IN_IGNORES);
|
|
87
|
+
try {
|
|
88
|
+
const content = await readFile2(join2(dir, ".distignore"), "utf-8");
|
|
89
|
+
ig.add(content);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const content = await readFile2(join2(dir, ".gitignore"), "utf-8");
|
|
94
|
+
ig.add(content);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
return ig;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/lib/archive.ts
|
|
101
|
+
import { create } from "tar";
|
|
102
|
+
import { stat, readdir } from "fs/promises";
|
|
103
|
+
import { join as join3, relative } from "path";
|
|
104
|
+
async function scanDir(dir, base, ig) {
|
|
105
|
+
const files = [];
|
|
106
|
+
let totalSize = 0;
|
|
107
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const fullPath = join3(dir, entry.name);
|
|
110
|
+
const relativePath = relative(base, fullPath);
|
|
111
|
+
if (ig.ignores(relativePath)) continue;
|
|
112
|
+
if (entry.isDirectory()) {
|
|
113
|
+
if (ig.ignores(relativePath + "/")) continue;
|
|
114
|
+
const sub = await scanDir(fullPath, base, ig);
|
|
115
|
+
files.push(...sub.files);
|
|
116
|
+
totalSize += sub.totalSize;
|
|
117
|
+
} else if (entry.isFile()) {
|
|
118
|
+
const fileStat = await stat(fullPath);
|
|
119
|
+
files.push(relativePath);
|
|
120
|
+
totalSize += fileStat.size;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { files, totalSize };
|
|
124
|
+
}
|
|
125
|
+
async function createArchive(dir, ig) {
|
|
126
|
+
const { files, totalSize } = await scanDir(dir, dir, ig);
|
|
127
|
+
if (files.length === 0) {
|
|
128
|
+
throw new Error("No files to deploy");
|
|
129
|
+
}
|
|
130
|
+
if (files.length > FREE_TIER.max_file_count) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Too many files (${files.length}). Maximum is ${FREE_TIER.max_file_count}.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (!files.includes("index.html") && !files.includes("index.htm")) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"No index.html found in the directory. Create one or run this from a build output directory."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
const fileStat = await stat(join3(dir, file));
|
|
142
|
+
if (fileStat.size > FREE_TIER.max_single_file_bytes) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`File "${file}" is too large (${(fileStat.size / 1024 / 1024).toFixed(1)} MB). Maximum is ${FREE_TIER.max_single_file_bytes / 1024 / 1024} MB.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const tarStream = create({ gzip: true, cwd: dir }, files);
|
|
149
|
+
const chunks = [];
|
|
150
|
+
let totalBytes = 0;
|
|
151
|
+
for await (const chunk of tarStream) {
|
|
152
|
+
const c = chunk;
|
|
153
|
+
const buf = new Uint8Array(c.buffer, c.byteOffset, c.byteLength);
|
|
154
|
+
chunks.push(buf);
|
|
155
|
+
totalBytes += buf.byteLength;
|
|
156
|
+
}
|
|
157
|
+
const buffer = new Uint8Array(totalBytes);
|
|
158
|
+
let offset = 0;
|
|
159
|
+
for (const chunk of chunks) {
|
|
160
|
+
buffer.set(chunk, offset);
|
|
161
|
+
offset += chunk.byteLength;
|
|
162
|
+
}
|
|
163
|
+
if (buffer.byteLength > FREE_TIER.max_deployment_size_bytes) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Compressed size (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB) exceeds limit (${FREE_TIER.max_deployment_size_bytes / 1024 / 1024} MB).`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return { buffer, files, totalSize };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/lib/api.ts
|
|
172
|
+
async function deploy(archive, apiToken) {
|
|
173
|
+
const response = await fetch(`${API_BASE_URL}/deploy`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
"content-type": "application/gzip",
|
|
177
|
+
authorization: `Bearer ${apiToken}`
|
|
178
|
+
},
|
|
179
|
+
body: archive.buffer.slice(
|
|
180
|
+
archive.byteOffset,
|
|
181
|
+
archive.byteOffset + archive.byteLength
|
|
182
|
+
)
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const error = await response.json();
|
|
186
|
+
throw new Error(error.error || `Deploy failed (${response.status})`);
|
|
187
|
+
}
|
|
188
|
+
return response.json();
|
|
189
|
+
}
|
|
190
|
+
async function deleteDeploy(subdomain, token) {
|
|
191
|
+
const response = await fetch(`${API_BASE_URL}/deploy/${subdomain}`, {
|
|
192
|
+
method: "DELETE",
|
|
193
|
+
headers: {
|
|
194
|
+
authorization: `Bearer ${token}`
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
const error = await response.json();
|
|
199
|
+
throw new Error(error.error || `Delete failed (${response.status})`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function sendCode(email) {
|
|
203
|
+
const response = await fetch(`${API_BASE_URL}/auth/send-code`, {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: { "content-type": "application/json" },
|
|
206
|
+
body: JSON.stringify({ email })
|
|
207
|
+
});
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
const error = await response.json();
|
|
210
|
+
throw new Error(error.error || `Failed to send code (${response.status})`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function verifyCode(email, code) {
|
|
214
|
+
const response = await fetch(`${API_BASE_URL}/auth/verify`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: { "content-type": "application/json" },
|
|
217
|
+
body: JSON.stringify({ email, code })
|
|
218
|
+
});
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
const error = await response.json();
|
|
221
|
+
throw new Error(error.error || `Verification failed (${response.status})`);
|
|
222
|
+
}
|
|
223
|
+
return response.json();
|
|
224
|
+
}
|
|
225
|
+
async function getMe(apiToken) {
|
|
226
|
+
const response = await fetch(`${API_BASE_URL}/auth/me`, {
|
|
227
|
+
headers: { authorization: `Bearer ${apiToken}` }
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const error = await response.json();
|
|
231
|
+
throw new Error(error.error || `Failed to get status (${response.status})`);
|
|
232
|
+
}
|
|
233
|
+
return response.json();
|
|
234
|
+
}
|
|
235
|
+
async function createToken(apiToken, name) {
|
|
236
|
+
const response = await fetch(`${API_BASE_URL}/auth/tokens`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: {
|
|
239
|
+
"content-type": "application/json",
|
|
240
|
+
authorization: `Bearer ${apiToken}`
|
|
241
|
+
},
|
|
242
|
+
body: JSON.stringify({ name })
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
const error = await response.json();
|
|
246
|
+
throw new Error(
|
|
247
|
+
error.error || `Failed to create token (${response.status})`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return response.json();
|
|
251
|
+
}
|
|
252
|
+
async function listTokens(apiToken) {
|
|
253
|
+
const response = await fetch(`${API_BASE_URL}/auth/tokens`, {
|
|
254
|
+
headers: { authorization: `Bearer ${apiToken}` }
|
|
255
|
+
});
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
const error = await response.json();
|
|
258
|
+
throw new Error(
|
|
259
|
+
error.error || `Failed to list tokens (${response.status})`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return response.json();
|
|
263
|
+
}
|
|
264
|
+
async function revokeToken(apiToken, name) {
|
|
265
|
+
const response = await fetch(
|
|
266
|
+
`${API_BASE_URL}/auth/tokens/${encodeURIComponent(name)}`,
|
|
267
|
+
{
|
|
268
|
+
method: "DELETE",
|
|
269
|
+
headers: { authorization: `Bearer ${apiToken}` }
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
const error = await response.json();
|
|
274
|
+
throw new Error(
|
|
275
|
+
error.error || `Failed to revoke token (${response.status})`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function logout(apiToken) {
|
|
280
|
+
const response = await fetch(`${API_BASE_URL}/auth/logout`, {
|
|
281
|
+
method: "DELETE",
|
|
282
|
+
headers: { authorization: `Bearer ${apiToken}` }
|
|
283
|
+
});
|
|
284
|
+
if (!response.ok) {
|
|
285
|
+
const error = await response.json();
|
|
286
|
+
throw new Error(error.error || `Logout failed (${response.status})`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/commands/deploy.ts
|
|
291
|
+
function formatBytes(bytes) {
|
|
292
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
293
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
294
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
295
|
+
}
|
|
296
|
+
function formatDate(iso) {
|
|
297
|
+
return new Date(iso).toLocaleDateString("en-US", {
|
|
298
|
+
month: "short",
|
|
299
|
+
day: "numeric",
|
|
300
|
+
year: "numeric"
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async function runDeploy(dir) {
|
|
304
|
+
const targetDir = resolve(dir || ".");
|
|
305
|
+
process.stdout.write("\x1B[90m\u25E6 Loading config...\x1B[0m\r");
|
|
306
|
+
const config = await getConfig();
|
|
307
|
+
if (!config?.api_token) {
|
|
308
|
+
process.stdout.write("\x1B[2K\r");
|
|
309
|
+
console.error(
|
|
310
|
+
"\x1B[31m\u2717 Not logged in. Run `dist login` to authenticate.\x1B[0m"
|
|
311
|
+
);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
process.stdout.write("\x1B[90m\u25E6 Scanning files...\x1B[0m\r");
|
|
315
|
+
const ig = await createIgnoreFilter(targetDir);
|
|
316
|
+
process.stdout.write("\x1B[90m\u25E6 Creating archive...\x1B[0m\r");
|
|
317
|
+
const archive = await createArchive(targetDir, ig);
|
|
318
|
+
process.stdout.write(
|
|
319
|
+
`\x1B[90m\u25E6 Uploading ${archive.files.length} files (${formatBytes(archive.buffer.byteLength)})...\x1B[0m\r`
|
|
320
|
+
);
|
|
321
|
+
let result;
|
|
322
|
+
try {
|
|
323
|
+
result = await deploy(archive.buffer, config.api_token);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
process.stdout.write("\x1B[2K\r");
|
|
326
|
+
console.error(`\x1B[31m\u2717 ${err.message}\x1B[0m`);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
process.stdout.write("\x1B[2K\r");
|
|
330
|
+
console.log(
|
|
331
|
+
`\x1B[32m\u2713\x1B[0m Deployed ${result.file_count} files (${formatBytes(result.total_size_bytes)})`
|
|
332
|
+
);
|
|
333
|
+
console.log();
|
|
334
|
+
console.log(` \x1B[36m\x1B[1m${result.url}\x1B[0m`);
|
|
335
|
+
console.log();
|
|
336
|
+
console.log(` Expires: ${formatDate(result.expires_at)} (7 days)`);
|
|
337
|
+
console.log(` Admin token: \x1B[33m${result.admin_token}\x1B[0m`);
|
|
338
|
+
console.log(` \x1B[90m(save this token to delete the deployment)\x1B[0m`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/commands/delete.ts
|
|
342
|
+
async function runDelete(subdomain, token) {
|
|
343
|
+
if (!token) {
|
|
344
|
+
const config = await getConfig();
|
|
345
|
+
if (!config?.api_token) {
|
|
346
|
+
console.error(
|
|
347
|
+
"Usage: dist delete <subdomain> --token <admin-token>\n or: dist login first, then: dist delete <subdomain>"
|
|
348
|
+
);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
token = config.api_token;
|
|
352
|
+
}
|
|
353
|
+
process.stdout.write("\x1B[90m\u25E6 Deleting deployment...\x1B[0m\r");
|
|
354
|
+
try {
|
|
355
|
+
await deleteDeploy(subdomain, token);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
process.stdout.write("\x1B[2K\r");
|
|
358
|
+
console.error(`\x1B[31m\u2717 ${err.message}\x1B[0m`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
process.stdout.write("\x1B[2K\r");
|
|
362
|
+
console.log(
|
|
363
|
+
`\x1B[32m\u2713\x1B[0m Deleted \x1B[36m${subdomain}.dist.site\x1B[0m`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/commands/auth.ts
|
|
368
|
+
import { createInterface } from "readline/promises";
|
|
369
|
+
import { stdin, stdout } from "process";
|
|
370
|
+
function prompt(rl, question) {
|
|
371
|
+
return rl.question(question);
|
|
372
|
+
}
|
|
373
|
+
async function runSignup() {
|
|
374
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
375
|
+
try {
|
|
376
|
+
const email = (await prompt(rl, " Enter your email: ")).trim();
|
|
377
|
+
if (!email || !email.includes("@")) {
|
|
378
|
+
console.error("\x1B[31m\u2717 Invalid email address\x1B[0m");
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
process.stdout.write(" Sending verification code...\r");
|
|
382
|
+
try {
|
|
383
|
+
await sendCode(email);
|
|
384
|
+
} catch (err) {
|
|
385
|
+
process.stdout.write("\x1B[2K\r");
|
|
386
|
+
console.error(`\x1B[31m\u2717 ${err.message}\x1B[0m`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
process.stdout.write("\x1B[2K\r");
|
|
390
|
+
console.log(" \x1B[32m\u2713\x1B[0m Code sent! Check your inbox.");
|
|
391
|
+
const code = (await prompt(rl, " Enter code: ")).trim();
|
|
392
|
+
if (!code) {
|
|
393
|
+
console.error("\x1B[31m\u2717 No code entered\x1B[0m");
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
let result;
|
|
397
|
+
try {
|
|
398
|
+
result = await verifyCode(email, code);
|
|
399
|
+
} catch (err) {
|
|
400
|
+
console.error(`\x1B[31m\u2717 ${err.message}\x1B[0m`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
await updateConfig({
|
|
404
|
+
api_token: result.api_token,
|
|
405
|
+
user_id: result.user_id
|
|
406
|
+
});
|
|
407
|
+
console.log(` \x1B[32m\u2713\x1B[0m Logged in as \x1B[36m${result.email}\x1B[0m`);
|
|
408
|
+
console.log(" API token saved to ~/.dist/config.json");
|
|
409
|
+
} finally {
|
|
410
|
+
rl.close();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function runLogout() {
|
|
414
|
+
const config = await getConfig();
|
|
415
|
+
if (!config?.api_token) {
|
|
416
|
+
console.log("Not logged in.");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
process.stdout.write("\x1B[90m\u25E6 Logging out...\x1B[0m\r");
|
|
420
|
+
try {
|
|
421
|
+
await logout(config.api_token);
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
await clearConfig();
|
|
425
|
+
process.stdout.write("\x1B[2K\r");
|
|
426
|
+
console.log("\x1B[32m\u2713\x1B[0m Logged out. Token revoked.");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/commands/tokens.ts
|
|
430
|
+
function requireAuth(config) {
|
|
431
|
+
if (!config?.api_token) {
|
|
432
|
+
console.error("\x1B[31m\u2717 Not logged in. Run `dist login` first.\x1B[0m");
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function runTokensList() {
|
|
437
|
+
const config = await getConfig();
|
|
438
|
+
requireAuth(config);
|
|
439
|
+
const result = await listTokens(config.api_token);
|
|
440
|
+
if (result.tokens.length === 0) {
|
|
441
|
+
console.log("No API tokens.");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
console.log("\x1B[1mAPI Tokens:\x1B[0m\n");
|
|
445
|
+
for (const token of result.tokens) {
|
|
446
|
+
const date = new Date(token.created_at).toLocaleDateString("en-US", {
|
|
447
|
+
month: "short",
|
|
448
|
+
day: "numeric",
|
|
449
|
+
year: "numeric"
|
|
450
|
+
});
|
|
451
|
+
console.log(` \x1B[36m${token.name}\x1B[0m \x1B[90mcreated ${date}\x1B[0m`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async function runTokensCreate(name) {
|
|
455
|
+
const config = await getConfig();
|
|
456
|
+
requireAuth(config);
|
|
457
|
+
const result = await createToken(config.api_token, name);
|
|
458
|
+
console.log(`\x1B[32m\u2713\x1B[0m Created token \x1B[36m${result.name}\x1B[0m`);
|
|
459
|
+
console.log();
|
|
460
|
+
console.log(` \x1B[33m${result.api_token}\x1B[0m`);
|
|
461
|
+
console.log();
|
|
462
|
+
console.log(" \x1B[90mSave this token \u2014 it won't be shown again.\x1B[0m");
|
|
463
|
+
}
|
|
464
|
+
async function runTokensRevoke(name) {
|
|
465
|
+
const config = await getConfig();
|
|
466
|
+
requireAuth(config);
|
|
467
|
+
await revokeToken(config.api_token, name);
|
|
468
|
+
console.log(`\x1B[32m\u2713\x1B[0m Revoked token \x1B[36m${name}\x1B[0m`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/commands/status.ts
|
|
472
|
+
async function runStatus() {
|
|
473
|
+
const config = await getConfig();
|
|
474
|
+
if (!config?.api_token) {
|
|
475
|
+
console.error("\x1B[31m\u2717 Not logged in. Run `dist login` first.\x1B[0m");
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
process.stdout.write("\x1B[90m\u25E6 Fetching account info...\x1B[0m\r");
|
|
479
|
+
let me;
|
|
480
|
+
try {
|
|
481
|
+
me = await getMe(config.api_token);
|
|
482
|
+
} catch (err) {
|
|
483
|
+
process.stdout.write("\x1B[2K\r");
|
|
484
|
+
console.error(`\x1B[31m\u2717 ${err.message}\x1B[0m`);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
process.stdout.write("\x1B[2K\r");
|
|
488
|
+
console.log(`\x1B[1mAccount\x1B[0m`);
|
|
489
|
+
console.log(` Email: \x1B[36m${me.email}\x1B[0m`);
|
|
490
|
+
console.log(` Tier: ${me.tier}`);
|
|
491
|
+
console.log(` ID: \x1B[90m${me.user_id}\x1B[0m`);
|
|
492
|
+
console.log();
|
|
493
|
+
console.log(`\x1B[1mUsage\x1B[0m`);
|
|
494
|
+
console.log(
|
|
495
|
+
` Deploys today: ${me.usage.deploys_today} / ${me.limits.deploys_per_day}`
|
|
496
|
+
);
|
|
497
|
+
console.log(
|
|
498
|
+
` Deploys this month: ${me.usage.deploys_this_month} / ${me.limits.deploys_per_month}`
|
|
499
|
+
);
|
|
500
|
+
console.log(
|
|
501
|
+
` Active deployments: ${me.usage.active_deployments} / ${me.limits.max_concurrent_deployments}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/index.ts
|
|
506
|
+
var args = process.argv.slice(2);
|
|
507
|
+
var command = args[0];
|
|
508
|
+
async function main() {
|
|
509
|
+
if (command === "signup" || command === "login") {
|
|
510
|
+
await runSignup();
|
|
511
|
+
} else if (command === "logout") {
|
|
512
|
+
await runLogout();
|
|
513
|
+
} else if (command === "status" || command === "whoami") {
|
|
514
|
+
await runStatus();
|
|
515
|
+
} else if (command === "tokens") {
|
|
516
|
+
const subcommand = args[1];
|
|
517
|
+
if (subcommand === "create") {
|
|
518
|
+
const name = args[2];
|
|
519
|
+
if (!name) {
|
|
520
|
+
console.error("Usage: dist tokens create <name>");
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
await runTokensCreate(name);
|
|
524
|
+
} else if (subcommand === "revoke") {
|
|
525
|
+
const name = args[2];
|
|
526
|
+
if (!name) {
|
|
527
|
+
console.error("Usage: dist tokens revoke <name>");
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
await runTokensRevoke(name);
|
|
531
|
+
} else {
|
|
532
|
+
await runTokensList();
|
|
533
|
+
}
|
|
534
|
+
} else if (command === "delete" || command === "rm") {
|
|
535
|
+
const subdomain = args[1];
|
|
536
|
+
const tokenFlagIdx = args.indexOf("--token");
|
|
537
|
+
const token = tokenFlagIdx !== -1 ? args[tokenFlagIdx + 1] : void 0;
|
|
538
|
+
if (!subdomain) {
|
|
539
|
+
console.error("Usage: dist delete <subdomain> [--token <admin-token>]");
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
await runDelete(subdomain, token);
|
|
543
|
+
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
544
|
+
printHelp();
|
|
545
|
+
} else if (command === "version" || command === "--version" || command === "-v") {
|
|
546
|
+
console.log("dist-site v0.1.0");
|
|
547
|
+
} else {
|
|
548
|
+
const dir = command && !command.startsWith("-") ? command : void 0;
|
|
549
|
+
await runDeploy(dir);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function printHelp() {
|
|
553
|
+
console.log(`
|
|
554
|
+
\x1B[1mdist-site\x1B[0m \u2014 Deploy static sites to random subdomains
|
|
555
|
+
|
|
556
|
+
\x1B[1mUsage:\x1B[0m
|
|
557
|
+
dist Deploy current directory
|
|
558
|
+
dist <dir> Deploy specific directory
|
|
559
|
+
dist login Log in with email verification
|
|
560
|
+
dist logout Log out and revoke token
|
|
561
|
+
dist status Show account and usage info
|
|
562
|
+
dist tokens List API tokens
|
|
563
|
+
dist tokens create <name> Create a named API token
|
|
564
|
+
dist tokens revoke <name> Revoke an API token
|
|
565
|
+
dist delete <id> [--token <t>] Delete a deployment
|
|
566
|
+
|
|
567
|
+
\x1B[1mOptions:\x1B[0m
|
|
568
|
+
-h, --help Show this help
|
|
569
|
+
-v, --version Show version
|
|
570
|
+
|
|
571
|
+
\x1B[1mExamples:\x1B[0m
|
|
572
|
+
$ npx dist-site login
|
|
573
|
+
$ npx dist-site
|
|
574
|
+
$ npx dist-site ./build
|
|
575
|
+
$ npx dist-site delete k7m2x9
|
|
576
|
+
$ npx dist-site tokens create ci
|
|
577
|
+
|
|
578
|
+
\x1B[90mhttps://dist.site\x1B[0m
|
|
579
|
+
`);
|
|
580
|
+
}
|
|
581
|
+
main().catch((err) => {
|
|
582
|
+
console.error(`\x1B[31m\u2717 ${err.message}\x1B[0m`);
|
|
583
|
+
process.exit(1);
|
|
584
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dist-site",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deploy static sites to random subdomains of dist.site",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dist": "./bin/dist.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"bin"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"deploy",
|
|
17
|
+
"static-site",
|
|
18
|
+
"hosting",
|
|
19
|
+
"preview"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"ignore": "^7.0.5",
|
|
24
|
+
"tar": "^7.5.7"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^24.0.0",
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"typescript": "^5.9.3",
|
|
30
|
+
"@dist-site/shared": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch"
|
|
35
|
+
}
|
|
36
|
+
}
|