packdog 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/dist/api.js +39 -0
- package/dist/commands/channels.js +17 -0
- package/dist/commands/info.js +15 -0
- package/dist/commands/init.js +23 -0
- package/dist/commands/publish.js +25 -0
- package/dist/commands/rollback.js +12 -0
- package/dist/commands/upload.js +64 -0
- package/dist/commands/versions.js +18 -0
- package/dist/config.js +63 -0
- package/dist/index.js +69 -0
- package/package.json +20 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function getBaseUrl() {
|
|
2
|
+
return process.env.PACKDOG_URL ?? "https://packdog.dev";
|
|
3
|
+
}
|
|
4
|
+
export async function fetchPackageName(packageId, token) {
|
|
5
|
+
const data = await fetchJson(`/packages/${packageId}`, token);
|
|
6
|
+
return data.name;
|
|
7
|
+
}
|
|
8
|
+
export async function fetchApi(path, token, options = {}) {
|
|
9
|
+
const url = `${getBaseUrl()}${path}`;
|
|
10
|
+
const headers = {
|
|
11
|
+
Authorization: `Bearer ${token}`,
|
|
12
|
+
...(options.headers ?? {})
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
return await fetch(url, { ...options, headers });
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
19
|
+
console.error(`Network error: ${msg}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function fetchJson(path, token, options = {}) {
|
|
24
|
+
const res = await fetchApi(path, token, options);
|
|
25
|
+
let data;
|
|
26
|
+
try {
|
|
27
|
+
data = await res.json();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
console.error(`Request failed (${res.status})`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const msg = data.error ?? `Request failed (${res.status})`;
|
|
35
|
+
console.error(msg);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getToken, getPackConfig } from "../config.js";
|
|
2
|
+
import { fetchJson, fetchPackageName } from "../api.js";
|
|
3
|
+
export async function channels() {
|
|
4
|
+
const token = getToken();
|
|
5
|
+
const config = await getPackConfig();
|
|
6
|
+
const data = await fetchJson(`/packages/${config.packageId}/channels`, token);
|
|
7
|
+
const name = await fetchPackageName(config.packageId, token);
|
|
8
|
+
console.log(`Package: ${name}\n`);
|
|
9
|
+
if (data.length === 0) {
|
|
10
|
+
console.log(" No channels yet");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const ch of data) {
|
|
14
|
+
const date = new Date(ch.updated_at).toISOString().slice(0, 19).replace("T", " ");
|
|
15
|
+
console.log(` ${ch.channel.padEnd(15)} ${ch.version_id} ${date}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getToken, getPackConfig } from "../config.js";
|
|
2
|
+
import { fetchJson, fetchPackageName } from "../api.js";
|
|
3
|
+
export async function info() {
|
|
4
|
+
const token = getToken();
|
|
5
|
+
const config = await getPackConfig();
|
|
6
|
+
const [name, versions, channels] = await Promise.all([
|
|
7
|
+
fetchPackageName(config.packageId, token),
|
|
8
|
+
fetchJson(`/packages/${config.packageId}/versions`, token),
|
|
9
|
+
fetchJson(`/packages/${config.packageId}/channels`, token),
|
|
10
|
+
]);
|
|
11
|
+
console.log(`Package: ${name}`);
|
|
12
|
+
console.log(` ID: ${config.packageId}`);
|
|
13
|
+
console.log(` Versions: ${versions.length}`);
|
|
14
|
+
console.log(` Channels: ${channels.length === 0 ? "none" : channels.map((c) => c.channel).join(", ")}`);
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getToken } from "../config.js";
|
|
4
|
+
import { fetchJson } from "../api.js";
|
|
5
|
+
export async function init(name) {
|
|
6
|
+
if (!name) {
|
|
7
|
+
console.error("Missing --name argument");
|
|
8
|
+
console.error("Usage: packdog init --name=my-pack");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const token = getToken();
|
|
12
|
+
console.log(`Registering pack: ${name}`);
|
|
13
|
+
const result = await fetchJson("/packages", token, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { "Content-Type": "application/json" },
|
|
16
|
+
body: JSON.stringify({ name })
|
|
17
|
+
});
|
|
18
|
+
const config = { packageId: result.packageId };
|
|
19
|
+
const configPath = join(process.cwd(), "packdog.json");
|
|
20
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
21
|
+
console.log(`Pack registered: ${result.packageId}`);
|
|
22
|
+
console.log(`Created packdog.json`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getToken, getPackConfig } from "../config.js";
|
|
2
|
+
import { fetchJson, fetchPackageName } from "../api.js";
|
|
3
|
+
export async function publish(channel, versionId) {
|
|
4
|
+
const token = getToken();
|
|
5
|
+
const config = await getPackConfig();
|
|
6
|
+
const ch = channel ?? "stable";
|
|
7
|
+
if (!versionId) {
|
|
8
|
+
const versions = await fetchJson(`/packages/${config.packageId}/versions`, token);
|
|
9
|
+
if (versions.length === 0) {
|
|
10
|
+
console.error("No versions found. Upload first: packdog upload");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
versionId = versions[0].id;
|
|
14
|
+
}
|
|
15
|
+
const name = await fetchPackageName(config.packageId, token);
|
|
16
|
+
console.log(`Package: ${name}`);
|
|
17
|
+
console.log(` Version: ${versionId}`);
|
|
18
|
+
console.log(` Channel: ${ch}`);
|
|
19
|
+
const result = await fetchJson(`/packages/${config.packageId}/channels/${ch}`, token, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify({ versionId })
|
|
23
|
+
});
|
|
24
|
+
console.log(`\nPublished to ${result.channel}: ${result.versionId}`);
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getToken, getPackConfig } from "../config.js";
|
|
2
|
+
import { fetchJson, fetchPackageName } from "../api.js";
|
|
3
|
+
export async function rollback(channel) {
|
|
4
|
+
const token = getToken();
|
|
5
|
+
const config = await getPackConfig();
|
|
6
|
+
const ch = channel ?? "stable";
|
|
7
|
+
const name = await fetchPackageName(config.packageId, token);
|
|
8
|
+
console.log(`Package: ${name}`);
|
|
9
|
+
console.log(` Channel: ${ch}`);
|
|
10
|
+
const result = await fetchJson(`/packages/${config.packageId}/channels/${ch}/rollback`, token, { method: "POST" });
|
|
11
|
+
console.log(`Rolled back to: ${result.versionId}`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { getToken, getPackConfig } from "../config.js";
|
|
4
|
+
import { fetchApi, fetchPackageName } from "../api.js";
|
|
5
|
+
async function getAllFiles(dir, baseDir = dir) {
|
|
6
|
+
const files = [];
|
|
7
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const fullPath = join(dir, entry.name);
|
|
10
|
+
if (entry.isDirectory()) {
|
|
11
|
+
files.push(...await getAllFiles(fullPath, baseDir));
|
|
12
|
+
}
|
|
13
|
+
else if (entry.isFile()) {
|
|
14
|
+
files.push({
|
|
15
|
+
path: relative(baseDir, fullPath),
|
|
16
|
+
content: await readFile(fullPath)
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return files;
|
|
21
|
+
}
|
|
22
|
+
export async function upload() {
|
|
23
|
+
const token = getToken();
|
|
24
|
+
const config = await getPackConfig();
|
|
25
|
+
const distPath = join(process.cwd(), "dist");
|
|
26
|
+
try {
|
|
27
|
+
await stat(distPath);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
console.error("dist/ folder not found. Build your pack first.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const files = await getAllFiles(distPath);
|
|
34
|
+
if (files.length === 0) {
|
|
35
|
+
console.error("No files found in dist/");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const name = await fetchPackageName(config.packageId, token);
|
|
39
|
+
console.log(`Package: ${name}`);
|
|
40
|
+
files.forEach((f) => console.log(` ${f.path} (${(f.content.length / 1024).toFixed(2)} KB)`));
|
|
41
|
+
const formData = new FormData();
|
|
42
|
+
for (const f of files) {
|
|
43
|
+
formData.append(f.path, new Blob([new Uint8Array(f.content)]), f.path);
|
|
44
|
+
}
|
|
45
|
+
const res = await fetchApi(`/packages/${config.packageId}/upload`, token, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: formData
|
|
48
|
+
});
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
result = await res.json();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
console.error(`Upload failed (${res.status})`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
console.error(result.error ?? "Upload failed");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const data = result;
|
|
62
|
+
console.log(`\nUploaded: ${data.versionId}`);
|
|
63
|
+
console.log(`Files: ${data.filesUploaded}, Size: ${(data.totalSize / 1024).toFixed(2)} KB`);
|
|
64
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getToken, getPackConfig } from "../config.js";
|
|
2
|
+
import { fetchJson, fetchPackageName } from "../api.js";
|
|
3
|
+
export async function versions() {
|
|
4
|
+
const token = getToken();
|
|
5
|
+
const config = await getPackConfig();
|
|
6
|
+
const data = await fetchJson(`/packages/${config.packageId}/versions`, token);
|
|
7
|
+
const name = await fetchPackageName(config.packageId, token);
|
|
8
|
+
console.log(`Package: ${name}\n`);
|
|
9
|
+
if (data.length === 0) {
|
|
10
|
+
console.log(" No versions yet");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const v of data) {
|
|
14
|
+
const date = new Date(v.created_at).toISOString().slice(0, 19).replace("T", " ");
|
|
15
|
+
const size = (v.total_size / 1024).toFixed(1);
|
|
16
|
+
console.log(` ${v.id} ${v.file_count} files ${size} KB ${date}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
function loadEnv() {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
let raw;
|
|
7
|
+
for (const file of [".env", ".env.development"]) {
|
|
8
|
+
try {
|
|
9
|
+
raw = readFileSync(join(cwd, file), "utf8");
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (!raw)
|
|
17
|
+
return;
|
|
18
|
+
for (const line of raw.split("\n")) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
21
|
+
continue;
|
|
22
|
+
const eq = trimmed.indexOf("=");
|
|
23
|
+
if (eq === -1)
|
|
24
|
+
continue;
|
|
25
|
+
const key = trimmed.slice(0, eq).trim();
|
|
26
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
27
|
+
// Strip surrounding quotes
|
|
28
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
29
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
30
|
+
value = value.slice(1, -1);
|
|
31
|
+
}
|
|
32
|
+
if (!process.env[key]) {
|
|
33
|
+
process.env[key] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function getToken() {
|
|
38
|
+
loadEnv();
|
|
39
|
+
const token = process.env.PACKDOG_TOKEN;
|
|
40
|
+
if (!token) {
|
|
41
|
+
console.error("Missing PACKDOG_TOKEN");
|
|
42
|
+
console.error("Set it in .env (PACKDOG_TOKEN=...) or as an environment variable");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
47
|
+
export async function getPackConfig() {
|
|
48
|
+
const configPath = join(process.cwd(), "packdog.json");
|
|
49
|
+
try {
|
|
50
|
+
const raw = await readFile(configPath, "utf8");
|
|
51
|
+
const config = JSON.parse(raw);
|
|
52
|
+
if (!config.packageId) {
|
|
53
|
+
console.error("packdog.json missing packageId");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
console.error("packdog.json not found in current directory");
|
|
60
|
+
console.error("Run 'packdog init' to create one");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { init } from "./commands/init.js";
|
|
3
|
+
import { upload } from "./commands/upload.js";
|
|
4
|
+
import { publish } from "./commands/publish.js";
|
|
5
|
+
import { rollback } from "./commands/rollback.js";
|
|
6
|
+
import { versions } from "./commands/versions.js";
|
|
7
|
+
import { channels } from "./commands/channels.js";
|
|
8
|
+
import { info } from "./commands/info.js";
|
|
9
|
+
const command = process.argv[2];
|
|
10
|
+
function getArg(name) {
|
|
11
|
+
const prefix = `--${name}=`;
|
|
12
|
+
const arg = process.argv.find((a) => a.startsWith(prefix));
|
|
13
|
+
return arg?.slice(prefix.length);
|
|
14
|
+
}
|
|
15
|
+
const HELP = `packdog — CLI for the Packdog package registry
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
init Register a new pack and create packdog.json
|
|
19
|
+
info Show pack name, versions and channels
|
|
20
|
+
upload Upload dist/ as a new version
|
|
21
|
+
publish Publish latest version to a channel
|
|
22
|
+
rollback Roll back a channel to its previous version
|
|
23
|
+
versions List all versions
|
|
24
|
+
channels List all channels
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--channel=<name> Channel name (default: stable)
|
|
28
|
+
--version=<id> Specific version ID (for publish)
|
|
29
|
+
--name=<name> Pack name (for init)
|
|
30
|
+
|
|
31
|
+
Environment (set in .env or .env.development):
|
|
32
|
+
PACKDOG_TOKEN API token (required)
|
|
33
|
+
PACKDOG_URL API URL (default: https://packdog.dev)
|
|
34
|
+
`;
|
|
35
|
+
async function main() {
|
|
36
|
+
switch (command) {
|
|
37
|
+
case "init":
|
|
38
|
+
await init(getArg("name"));
|
|
39
|
+
break;
|
|
40
|
+
case "upload":
|
|
41
|
+
await upload();
|
|
42
|
+
break;
|
|
43
|
+
case "publish":
|
|
44
|
+
await publish(getArg("channel"), getArg("version"));
|
|
45
|
+
break;
|
|
46
|
+
case "rollback":
|
|
47
|
+
await rollback(getArg("channel"));
|
|
48
|
+
break;
|
|
49
|
+
case "versions":
|
|
50
|
+
await versions();
|
|
51
|
+
break;
|
|
52
|
+
case "channels":
|
|
53
|
+
await channels();
|
|
54
|
+
break;
|
|
55
|
+
case "info":
|
|
56
|
+
await info();
|
|
57
|
+
break;
|
|
58
|
+
case "--help":
|
|
59
|
+
case "-h":
|
|
60
|
+
case undefined:
|
|
61
|
+
console.log(HELP);
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
console.error(`Unknown command: ${command}`);
|
|
65
|
+
console.log(HELP);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "packdog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the Packdog package registry",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"packdog": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^25.3.5",
|
|
18
|
+
"typescript": "^5.9.3"
|
|
19
|
+
}
|
|
20
|
+
}
|