@freeappstore/cli 0.3.3 → 0.4.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/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +47 -11
- package/dist/commands/list.js.map +1 -1
- package/dist/lib/history.d.ts +29 -0
- package/dist/lib/history.d.ts.map +1 -0
- package/dist/lib/history.js +77 -0
- package/dist/lib/history.js.map +1 -0
- package/dist/lib/history.test.d.ts +2 -0
- package/dist/lib/history.test.d.ts.map +1 -0
- package/dist/lib/history.test.js +30 -0
- package/dist/lib/history.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,eAAO,MAAM,WAAW,SA0EpB,CAAC"}
|
package/dist/commands/list.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { readConfig } from '../lib/config.js';
|
|
3
|
+
import { fetchAppHistory, formatRelative } from '../lib/history.js';
|
|
3
4
|
const isTTY = Boolean(process.stdout.isTTY) && process.env['NO_COLOR'] !== '1';
|
|
4
5
|
const dim = (s) => (isTTY ? `\x1b[2m${s}\x1b[22m` : s);
|
|
5
6
|
const bold = (s) => (isTTY ? `\x1b[1m${s}\x1b[22m` : s);
|
|
6
7
|
export const listCommand = new Command('list')
|
|
7
8
|
.alias('ls')
|
|
8
|
-
.description('List apps you have published
|
|
9
|
-
.option('--json', 'Output JSON instead of a table.')
|
|
9
|
+
.description('List apps and games you have published.')
|
|
10
|
+
.option('--json', 'Output JSON instead of a table (includes commit history when -v).')
|
|
11
|
+
.option('-v, --verbose', 'Show recent commits per app. Fetches the last 3 commits from each app repo via the GitHub API.')
|
|
10
12
|
.action(async (opts) => {
|
|
11
13
|
const config = await readConfig();
|
|
12
14
|
if (!config.session?.token) {
|
|
@@ -26,8 +28,16 @@ export const listCommand = new Command('list')
|
|
|
26
28
|
process.exit(1);
|
|
27
29
|
}
|
|
28
30
|
const { apps } = (await res.json());
|
|
31
|
+
// Fetch commit history in parallel — small set (a creator's own apps),
|
|
32
|
+
// capped at 3 commits each, gracefully degrades on rate-limit (the
|
|
33
|
+
// app prints fine without history). GH_TOKEN env var lifts the
|
|
34
|
+
// 60/hr anonymous limit if the user runs into it.
|
|
35
|
+
const histories = apps.length > 0
|
|
36
|
+
? await Promise.all(apps.map((a) => fetchAppHistory(repoSlug(a.repoUrl))))
|
|
37
|
+
: [];
|
|
29
38
|
if (opts.json) {
|
|
30
|
-
|
|
39
|
+
const enriched = apps.map((a, i) => ({ ...a, history: histories[i] }));
|
|
40
|
+
process.stdout.write(JSON.stringify(enriched, null, 2) + '\n');
|
|
31
41
|
return;
|
|
32
42
|
}
|
|
33
43
|
if (apps.length === 0) {
|
|
@@ -35,14 +45,10 @@ export const listCommand = new Command('list')
|
|
|
35
45
|
return;
|
|
36
46
|
}
|
|
37
47
|
process.stdout.write('\n');
|
|
38
|
-
for (
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
process.stdout.write(` ${a.oneliner}\n`);
|
|
43
|
-
process.stdout.write(` ${dim('live:')} ${a.appUrl}\n`);
|
|
44
|
-
process.stdout.write(` ${dim('repo:')} ${a.repoUrl}\n`);
|
|
45
|
-
process.stdout.write('\n');
|
|
48
|
+
for (let i = 0; i < apps.length; i++) {
|
|
49
|
+
const a = apps[i];
|
|
50
|
+
const h = histories[i];
|
|
51
|
+
printApp(a, h, opts.verbose ?? false);
|
|
46
52
|
}
|
|
47
53
|
const gameCount = apps.filter((a) => a.store === 'games').length;
|
|
48
54
|
const appCount = apps.length - gameCount;
|
|
@@ -52,5 +58,35 @@ export const listCommand = new Command('list')
|
|
|
52
58
|
? `${gameCount} game${gameCount === 1 ? '' : 's'}`
|
|
53
59
|
: `${appCount} app${appCount === 1 ? '' : 's'}, ${gameCount} game${gameCount === 1 ? '' : 's'}`;
|
|
54
60
|
process.stdout.write(dim(`${summary}\n`));
|
|
61
|
+
// Surface rate-limit failure as a hint, not an error — output above
|
|
62
|
+
// is still useful, just lacks recency info.
|
|
63
|
+
if (histories.some((h) => h.error === 'rate-limited')) {
|
|
64
|
+
process.stdout.write(dim('Some commit history was rate-limited. Set GH_TOKEN to a GitHub PAT to lift the 60/hr anonymous limit.\n'));
|
|
65
|
+
}
|
|
55
66
|
});
|
|
67
|
+
function printApp(a, h, verbose) {
|
|
68
|
+
const storeBadge = a.store === 'games' ? dim('[game] ') : dim('[app] ');
|
|
69
|
+
process.stdout.write(`${storeBadge}${bold(a.id.padEnd(20))} ${dim(a.category ?? '—')}\n`);
|
|
70
|
+
if (a.oneliner)
|
|
71
|
+
process.stdout.write(` ${a.oneliner}\n`);
|
|
72
|
+
if (h.lastUpdated) {
|
|
73
|
+
process.stdout.write(` ${dim('updated:')} ${formatRelative(h.lastUpdated)}\n`);
|
|
74
|
+
}
|
|
75
|
+
process.stdout.write(` ${dim('live:')} ${a.appUrl}\n`);
|
|
76
|
+
process.stdout.write(` ${dim('repo:')} ${a.repoUrl}\n`);
|
|
77
|
+
if (verbose && h.commits && h.commits.length > 0) {
|
|
78
|
+
process.stdout.write(` ${dim('recent:')}\n`);
|
|
79
|
+
for (const c of h.commits) {
|
|
80
|
+
const short = c.sha.slice(0, 7);
|
|
81
|
+
const msg = c.message.split('\n')[0].slice(0, 80);
|
|
82
|
+
process.stdout.write(` ${dim(short)} ${msg}\n`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write('\n');
|
|
86
|
+
}
|
|
87
|
+
/** Convert "https://github.com/owner/name" → "owner/name". */
|
|
88
|
+
function repoSlug(url) {
|
|
89
|
+
const m = /github\.com\/([^/]+)\/([^/.]+)/.exec(url);
|
|
90
|
+
return m ? `${m[1]}/${m[2]}` : '';
|
|
91
|
+
}
|
|
56
92
|
//# sourceMappingURL=list.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub commit history helper, used by `fas list` (and any future
|
|
3
|
+
* command that needs "what changed recently" per app).
|
|
4
|
+
*
|
|
5
|
+
* Reads GH_TOKEN / GITHUB_TOKEN from env to lift the 60/hr anonymous
|
|
6
|
+
* rate limit. Failures degrade silently — the caller decides whether
|
|
7
|
+
* to show "rate-limited" or just nothing.
|
|
8
|
+
*/
|
|
9
|
+
export interface CommitInfo {
|
|
10
|
+
sha: string;
|
|
11
|
+
message: string;
|
|
12
|
+
authoredAt: string;
|
|
13
|
+
htmlUrl: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AppHistory {
|
|
16
|
+
/** ISO-8601 of the most recent commit, or null if unavailable. */
|
|
17
|
+
lastUpdated: string | null;
|
|
18
|
+
/** Up to 3 most recent commits, newest first. Empty if unavailable. */
|
|
19
|
+
commits: CommitInfo[];
|
|
20
|
+
/** "rate-limited" | "not-found" | undefined */
|
|
21
|
+
error?: 'rate-limited' | 'not-found' | 'network';
|
|
22
|
+
}
|
|
23
|
+
export declare function fetchAppHistory(slug: string): Promise<AppHistory>;
|
|
24
|
+
/**
|
|
25
|
+
* Human-friendly relative time. "5 minutes ago", "yesterday", "3 days ago",
|
|
26
|
+
* "2 months ago". Tuned for terminal output, not pluralization-perfect.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatRelative(iso: string, now?: Date): string;
|
|
29
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/lib/history.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uEAAuE;IACvE,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,cAAc,GAAG,WAAW,GAAG,SAAS,CAAC;CAClD;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA0CvE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAgB1E"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub commit history helper, used by `fas list` (and any future
|
|
3
|
+
* command that needs "what changed recently" per app).
|
|
4
|
+
*
|
|
5
|
+
* Reads GH_TOKEN / GITHUB_TOKEN from env to lift the 60/hr anonymous
|
|
6
|
+
* rate limit. Failures degrade silently — the caller decides whether
|
|
7
|
+
* to show "rate-limited" or just nothing.
|
|
8
|
+
*/
|
|
9
|
+
const EMPTY = { lastUpdated: null, commits: [] };
|
|
10
|
+
export async function fetchAppHistory(slug) {
|
|
11
|
+
if (!slug)
|
|
12
|
+
return EMPTY;
|
|
13
|
+
const headers = {
|
|
14
|
+
'User-Agent': 'fas-cli',
|
|
15
|
+
Accept: 'application/vnd.github+json',
|
|
16
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
17
|
+
};
|
|
18
|
+
const token = process.env['GH_TOKEN'] ?? process.env['GITHUB_TOKEN'];
|
|
19
|
+
if (token)
|
|
20
|
+
headers.Authorization = `Bearer ${token}`;
|
|
21
|
+
let res;
|
|
22
|
+
try {
|
|
23
|
+
res = await fetch(`https://api.github.com/repos/${slug}/commits?per_page=3`, { headers });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { ...EMPTY, error: 'network' };
|
|
27
|
+
}
|
|
28
|
+
if (res.status === 404)
|
|
29
|
+
return { ...EMPTY, error: 'not-found' };
|
|
30
|
+
if (res.status === 403) {
|
|
31
|
+
const body = await res.text().catch(() => '');
|
|
32
|
+
if (/rate limit/i.test(body))
|
|
33
|
+
return { ...EMPTY, error: 'rate-limited' };
|
|
34
|
+
return { ...EMPTY, error: 'rate-limited' }; // 403 + no body usually = rate-limit on github
|
|
35
|
+
}
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
return EMPTY;
|
|
38
|
+
const raw = (await res.json().catch(() => []));
|
|
39
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
40
|
+
return EMPTY;
|
|
41
|
+
const commits = raw.map((c) => ({
|
|
42
|
+
sha: c.sha,
|
|
43
|
+
message: c.commit.message,
|
|
44
|
+
authoredAt: c.commit.author?.date ?? c.commit.committer?.date ?? '',
|
|
45
|
+
htmlUrl: c.html_url,
|
|
46
|
+
}));
|
|
47
|
+
return { lastUpdated: commits[0]?.authoredAt ?? null, commits };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Human-friendly relative time. "5 minutes ago", "yesterday", "3 days ago",
|
|
51
|
+
* "2 months ago". Tuned for terminal output, not pluralization-perfect.
|
|
52
|
+
*/
|
|
53
|
+
export function formatRelative(iso, now = new Date()) {
|
|
54
|
+
const t = new Date(iso).getTime();
|
|
55
|
+
if (Number.isNaN(t))
|
|
56
|
+
return iso;
|
|
57
|
+
const diffSec = Math.max(1, Math.round((now.getTime() - t) / 1000));
|
|
58
|
+
if (diffSec < 60)
|
|
59
|
+
return `${diffSec}s ago`;
|
|
60
|
+
const diffMin = Math.round(diffSec / 60);
|
|
61
|
+
if (diffMin < 60)
|
|
62
|
+
return `${diffMin} min ago`;
|
|
63
|
+
const diffHr = Math.round(diffMin / 60);
|
|
64
|
+
if (diffHr < 24)
|
|
65
|
+
return `${diffHr}h ago`;
|
|
66
|
+
const diffDay = Math.round(diffHr / 24);
|
|
67
|
+
if (diffDay === 1)
|
|
68
|
+
return 'yesterday';
|
|
69
|
+
if (diffDay < 30)
|
|
70
|
+
return `${diffDay} days ago`;
|
|
71
|
+
const diffMonth = Math.round(diffDay / 30);
|
|
72
|
+
if (diffMonth < 12)
|
|
73
|
+
return `${diffMonth} month${diffMonth === 1 ? '' : 's'} ago`;
|
|
74
|
+
const diffYear = Math.round(diffMonth / 12);
|
|
75
|
+
return `${diffYear} year${diffYear === 1 ? '' : 's'} ago`;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/lib/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.test.d.ts","sourceRoot":"","sources":["../../src/lib/history.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { formatRelative } from './history.js';
|
|
3
|
+
describe('formatRelative', () => {
|
|
4
|
+
const now = new Date('2026-05-06T12:00:00Z');
|
|
5
|
+
it('seconds', () => {
|
|
6
|
+
expect(formatRelative('2026-05-06T11:59:30Z', now)).toBe('30s ago');
|
|
7
|
+
});
|
|
8
|
+
it('minutes', () => {
|
|
9
|
+
expect(formatRelative('2026-05-06T11:55:00Z', now)).toBe('5 min ago');
|
|
10
|
+
});
|
|
11
|
+
it('hours', () => {
|
|
12
|
+
expect(formatRelative('2026-05-06T08:00:00Z', now)).toBe('4h ago');
|
|
13
|
+
});
|
|
14
|
+
it('yesterday singular', () => {
|
|
15
|
+
expect(formatRelative('2026-05-05T12:00:00Z', now)).toBe('yesterday');
|
|
16
|
+
});
|
|
17
|
+
it('days', () => {
|
|
18
|
+
expect(formatRelative('2026-05-01T12:00:00Z', now)).toBe('5 days ago');
|
|
19
|
+
});
|
|
20
|
+
it('months', () => {
|
|
21
|
+
expect(formatRelative('2026-02-06T12:00:00Z', now)).toBe('3 months ago');
|
|
22
|
+
});
|
|
23
|
+
it('years', () => {
|
|
24
|
+
expect(formatRelative('2024-05-06T12:00:00Z', now)).toBe('2 years ago');
|
|
25
|
+
});
|
|
26
|
+
it('returns the input when not a valid date', () => {
|
|
27
|
+
expect(formatRelative('not-a-date', now)).toBe('not-a-date');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=history.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.test.js","sourceRoot":"","sources":["../../src/lib/history.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAE7C,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACjB,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACjB,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACf,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACd,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QAChB,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACf,MAAM,CAAC,cAAc,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|