@pdpp/cli 0.1.0-beta.2 → 0.1.0-beta.4

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.
@@ -0,0 +1,76 @@
1
+ export function resolveFormat(flags, defaultWhenTty = 'json', defaultWhenPipe = 'json') {
2
+ return flags.format || (process.stdout.isTTY ? defaultWhenTty : defaultWhenPipe);
3
+ }
4
+
5
+ export function writeData(data, format = 'json', out = process.stdout) {
6
+ if (format === 'json') {
7
+ out.write(`${JSON.stringify(data, null, 2)}\n`);
8
+ return;
9
+ }
10
+
11
+ if (format === 'jsonl') {
12
+ if (!Array.isArray(data)) {
13
+ out.write(`${JSON.stringify(data)}\n`);
14
+ return;
15
+ }
16
+ for (const item of data) {
17
+ out.write(`${JSON.stringify(item)}\n`);
18
+ }
19
+ return;
20
+ }
21
+
22
+ if (format === 'table') {
23
+ writeTable(data, out);
24
+ return;
25
+ }
26
+
27
+ throw new Error(`Unsupported format: ${format}`);
28
+ }
29
+
30
+ function writeTable(data, out = process.stdout) {
31
+ const rows = Array.isArray(data) ? data : [data];
32
+ if (!rows.length) {
33
+ out.write('(empty)\n');
34
+ return;
35
+ }
36
+
37
+ const normalized = rows.map((row) => flattenRow(row));
38
+ const columns = Array.from(
39
+ normalized.reduce((acc, row) => {
40
+ Object.keys(row).forEach((key) => acc.add(key));
41
+ return acc;
42
+ }, new Set())
43
+ );
44
+
45
+ const widths = Object.fromEntries(
46
+ columns.map((column) => [
47
+ column,
48
+ Math.max(column.length, ...normalized.map((row) => String(row[column] ?? '').length)),
49
+ ])
50
+ );
51
+
52
+ const renderRow = (row) =>
53
+ columns
54
+ .map((column) => String(row[column] ?? '').padEnd(widths[column]))
55
+ .join(' ');
56
+
57
+ out.write(`${renderRow(Object.fromEntries(columns.map((column) => [column, column])))}\n`);
58
+ out.write(`${columns.map((column) => '-'.repeat(widths[column])).join(' ')}\n`);
59
+ for (const row of normalized) {
60
+ out.write(`${renderRow(row)}\n`);
61
+ }
62
+ }
63
+
64
+ function flattenRow(row) {
65
+ const out = {};
66
+ for (const [key, value] of Object.entries(row)) {
67
+ if (Array.isArray(value)) {
68
+ out[key] = value.join(', ');
69
+ } else if (value && typeof value === 'object') {
70
+ out[key] = JSON.stringify(value);
71
+ } else {
72
+ out[key] = value ?? '';
73
+ }
74
+ }
75
+ return out;
76
+ }
@@ -0,0 +1,107 @@
1
+ import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+
4
+ import { writePdppSecretFile } from '../cache-layout.js';
5
+ import { PdppCliError } from './errors.js';
6
+
7
+ const SESSION_DIR_NAME = 'owner-sessions';
8
+
9
+ export function getOwnerSessionPaths(referenceUrl, opts = {}) {
10
+ const cacheRoot = opts.cacheRoot || '.pdpp';
11
+ const dir = join(cacheRoot, SESSION_DIR_NAME);
12
+ const file = join(dir, `${sessionCacheKey(referenceUrl)}.json`);
13
+ return { cacheRoot, dir, file };
14
+ }
15
+
16
+ function sessionCacheKey(referenceUrl) {
17
+ try {
18
+ const u = new URL(referenceUrl);
19
+ // Owner sessions are origin-scoped. Include protocol so an HTTPS login
20
+ // cannot be silently reused for an HTTP reference URL on the same host.
21
+ return `${u.protocol}//${u.host}`.replace(/[^a-zA-Z0-9.-]/g, '_');
22
+ } catch {
23
+ return referenceUrl.replace(/[^a-zA-Z0-9.-]/g, '_');
24
+ }
25
+ }
26
+
27
+ export function writeOwnerSession({ referenceUrl, cookie, cacheRoot } = {}) {
28
+ if (!referenceUrl) throw new PdppCliError('writeOwnerSession requires referenceUrl');
29
+ if (!cookie) throw new PdppCliError('writeOwnerSession requires cookie');
30
+
31
+ const { file } = getOwnerSessionPaths(referenceUrl, { cacheRoot });
32
+ const payload = {
33
+ reference_url: referenceUrl,
34
+ cookie,
35
+ saved_at: new Date().toISOString(),
36
+ };
37
+ writePdppSecretFile(file, JSON.stringify(payload, null, 2));
38
+ ensureGitignore(cacheRoot || '.pdpp');
39
+ return file;
40
+ }
41
+
42
+ export function readOwnerSession({ referenceUrl, cacheRoot } = {}) {
43
+ if (!referenceUrl) return null;
44
+ const { file } = getOwnerSessionPaths(referenceUrl, { cacheRoot });
45
+ if (!existsSync(file)) return null;
46
+ try {
47
+ const data = JSON.parse(readFileSync(file, 'utf8'));
48
+ if (data && typeof data.cookie === 'string' && data.cookie.length > 0) {
49
+ return { cookie: data.cookie, savedAt: data.saved_at || null, file };
50
+ }
51
+ } catch {
52
+ return null;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ export function clearOwnerSession({ referenceUrl, cacheRoot } = {}) {
58
+ if (!referenceUrl) return false;
59
+ const { file } = getOwnerSessionPaths(referenceUrl, { cacheRoot });
60
+ if (!existsSync(file)) return false;
61
+ try {
62
+ unlinkSync(file);
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ export function getOwnerSessionFileMode(referenceUrl, opts = {}) {
70
+ const { file } = getOwnerSessionPaths(referenceUrl, opts);
71
+ if (!existsSync(file)) return null;
72
+ return statSync(file).mode & 0o777;
73
+ }
74
+
75
+ function ensureGitignore(cacheRoot) {
76
+ const gi = join(cacheRoot, '.gitignore');
77
+ try {
78
+ mkdirSync(dirname(gi), { recursive: true, mode: 0o700 });
79
+ if (!existsSync(gi)) {
80
+ writeFileSync(gi, '*\n!.gitignore\n', { mode: 0o600 });
81
+ }
82
+ } catch {
83
+ // best-effort; never block CLI on .gitignore creation
84
+ }
85
+ }
86
+
87
+ // Parse a Set-Cookie header value (or array of values) and return the value of
88
+ // the named cookie if present, e.g. "pdpp_owner_session=abc; Path=/; HttpOnly".
89
+ export function extractCookieFromSetCookie(setCookie, cookieName) {
90
+ if (!setCookie) return null;
91
+ const headers = Array.isArray(setCookie) ? setCookie : [setCookie];
92
+ for (const raw of headers) {
93
+ if (typeof raw !== 'string') continue;
94
+ // Set-Cookie may contain multiple cookies joined by ", " when collapsed.
95
+ // Split conservatively on the first attribute pair only.
96
+ for (const piece of raw.split(/,\s*(?=[^;]+=[^;]+)/)) {
97
+ const [pair] = piece.split(';');
98
+ if (!pair) continue;
99
+ const eq = pair.indexOf('=');
100
+ if (eq === -1) continue;
101
+ const name = pair.slice(0, eq).trim();
102
+ const value = pair.slice(eq + 1).trim();
103
+ if (name === cookieName) return value;
104
+ }
105
+ }
106
+ return null;
107
+ }