@aigne/afs-github-cost 1.11.0-beta.12
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.md +26 -0
- package/dist/adapter.cjs +215 -0
- package/dist/adapter.d.cts +66 -0
- package/dist/adapter.d.cts.map +1 -0
- package/dist/adapter.d.mts +66 -0
- package/dist/adapter.d.mts.map +1 -0
- package/dist/adapter.mjs +215 -0
- package/dist/adapter.mjs.map +1 -0
- package/dist/index.cjs +101 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +95 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. Unauthorized copying, modification, distribution, or use of
|
|
7
|
+
this Software, via any medium, is strictly prohibited.
|
|
8
|
+
|
|
9
|
+
The Software is provided for internal use only within ArcBlock, Inc. and its
|
|
10
|
+
authorized affiliates.
|
|
11
|
+
|
|
12
|
+
## No License Granted
|
|
13
|
+
|
|
14
|
+
No license, express or implied, is granted to any party for any purpose.
|
|
15
|
+
All rights are reserved by ArcBlock, Inc.
|
|
16
|
+
|
|
17
|
+
## Public Artifact Distribution
|
|
18
|
+
|
|
19
|
+
Portions of this Software may be released publicly under separate open-source
|
|
20
|
+
licenses (such as MIT License) through designated public repositories. Such
|
|
21
|
+
public releases are governed by their respective licenses and do not affect
|
|
22
|
+
the proprietary nature of this repository.
|
|
23
|
+
|
|
24
|
+
## Contact
|
|
25
|
+
|
|
26
|
+
For licensing inquiries, contact: legal@arcblock.io
|
package/dist/adapter.cjs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/adapter.ts
|
|
3
|
+
const SERVICE_NAME_MAP = {
|
|
4
|
+
actions: "actions",
|
|
5
|
+
packages: "packages",
|
|
6
|
+
codespaces: "codespaces",
|
|
7
|
+
copilot: "copilot",
|
|
8
|
+
"shared-storage": "shared-storage",
|
|
9
|
+
"git-lfs-data": "git-lfs",
|
|
10
|
+
"actions-minutes": "actions",
|
|
11
|
+
"actions-storage": "actions-storage",
|
|
12
|
+
"codespaces-compute": "codespaces-compute",
|
|
13
|
+
"codespaces-prebuild": "codespaces-prebuild",
|
|
14
|
+
"copilot-business": "copilot",
|
|
15
|
+
"copilot-enterprise": "copilot"
|
|
16
|
+
};
|
|
17
|
+
function normalizeServiceName(product) {
|
|
18
|
+
if (!product) return "unknown";
|
|
19
|
+
const lower = product.toLowerCase();
|
|
20
|
+
if (SERVICE_NAME_MAP[lower]) return SERVICE_NAME_MAP[lower];
|
|
21
|
+
return lower.replace(/\s+/g, "-");
|
|
22
|
+
}
|
|
23
|
+
const PLAN_PRICE_MAP = {
|
|
24
|
+
free: 0,
|
|
25
|
+
team: 4,
|
|
26
|
+
"business-plus": 21,
|
|
27
|
+
enterprise: 21
|
|
28
|
+
};
|
|
29
|
+
function planPricePerSeat(planName) {
|
|
30
|
+
return PLAN_PRICE_MAP[planName.toLowerCase()] ?? 0;
|
|
31
|
+
}
|
|
32
|
+
/** Number of days in the month that contains `dateStr` (YYYY-MM-DD). */
|
|
33
|
+
function daysInMonth(dateStr) {
|
|
34
|
+
const { year, month } = parseDateParts(dateStr);
|
|
35
|
+
return new Date(year, month, 0).getDate();
|
|
36
|
+
}
|
|
37
|
+
/** Parse "YYYY-MM-DD" into { year, month, day } */
|
|
38
|
+
function parseDateParts(dateStr) {
|
|
39
|
+
const parts = dateStr.split("-").map(Number);
|
|
40
|
+
return {
|
|
41
|
+
year: parts[0] ?? 0,
|
|
42
|
+
month: parts[1] ?? 0,
|
|
43
|
+
day: parts[2] ?? 0
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Iterate over each day in [startDate, endDate) */
|
|
47
|
+
function* eachDay(startDate, endDate) {
|
|
48
|
+
const start = /* @__PURE__ */ new Date(`${startDate}T00:00:00Z`);
|
|
49
|
+
const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
|
|
50
|
+
const current = new Date(start);
|
|
51
|
+
while (current < end) {
|
|
52
|
+
yield current.toISOString().slice(0, 10);
|
|
53
|
+
current.setUTCDate(current.getUTCDate() + 1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Map over items with bounded concurrency. No external dependencies. */
|
|
57
|
+
async function pMap(items, fn, concurrency) {
|
|
58
|
+
const results = new Array(items.length);
|
|
59
|
+
let index = 0;
|
|
60
|
+
async function next() {
|
|
61
|
+
const i = index++;
|
|
62
|
+
if (i >= items.length) return;
|
|
63
|
+
results[i] = await fn(items[i]);
|
|
64
|
+
return next();
|
|
65
|
+
}
|
|
66
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => next()));
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
const BILLING_API_BASE = "https://api.github.com/orgs";
|
|
70
|
+
var GitHubCostAdapter = class {
|
|
71
|
+
cloud = "github";
|
|
72
|
+
config;
|
|
73
|
+
client;
|
|
74
|
+
cachedServices = null;
|
|
75
|
+
constructor(config = {}, client) {
|
|
76
|
+
this.config = config;
|
|
77
|
+
this.client = client;
|
|
78
|
+
}
|
|
79
|
+
async isAvailable() {
|
|
80
|
+
if (!this.config.token || !this.config.org) return false;
|
|
81
|
+
try {
|
|
82
|
+
const client = this.getClient();
|
|
83
|
+
if (!client) return false;
|
|
84
|
+
const { year, month, day } = parseDateParts((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
85
|
+
const url = `${BILLING_API_BASE}/${this.config.org}/settings/billing/usage?year=${year}&month=${month}&day=${day}`;
|
|
86
|
+
return (await client.get(url, { headers: this.buildHeaders() })).status === 200;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async fetchCosts(options) {
|
|
92
|
+
const client = this.getClient();
|
|
93
|
+
if (!client) throw new Error("GitHub HTTP client not available");
|
|
94
|
+
const org = this.config.org;
|
|
95
|
+
if (!org) throw new Error("Missing org in GitHub config");
|
|
96
|
+
const headers = this.buildHeaders();
|
|
97
|
+
const days = [...eachDay(options.startDate, options.endDate)];
|
|
98
|
+
const orgPlan = await this.fetchOrgPlan(client, org, headers);
|
|
99
|
+
const records = (await pMap(days, async (day) => {
|
|
100
|
+
const { year, month, day: d } = parseDateParts(day);
|
|
101
|
+
const url = `${BILLING_API_BASE}/${org}/settings/billing/usage?year=${year}&month=${month}&day=${d}`;
|
|
102
|
+
const response = await client.get(url, { headers });
|
|
103
|
+
if (response.status === 403) {
|
|
104
|
+
const msg = response.data?.message ?? "";
|
|
105
|
+
if (typeof msg === "string" && msg.toLowerCase().includes("rate limit")) throw new Error(`GitHub API rate limit exceeded. Retry after a few minutes.`);
|
|
106
|
+
throw new Error("Insufficient permissions: requires read:billing scope");
|
|
107
|
+
}
|
|
108
|
+
if (response.status === 404) throw new Error("Organization not found or not on Enhanced Billing Platform");
|
|
109
|
+
if (response.status !== 200) throw new Error(`GitHub API error: status ${response.status}`);
|
|
110
|
+
const items = response.data.usageItems ?? [];
|
|
111
|
+
const records$1 = [];
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
const netAmount = item.netAmount ?? item.grossAmount - (item.discountAmount ?? 0);
|
|
114
|
+
const record = {
|
|
115
|
+
cloud: "github",
|
|
116
|
+
service: normalizeServiceName(item.product),
|
|
117
|
+
date: day,
|
|
118
|
+
amount: netAmount,
|
|
119
|
+
currency: "USD"
|
|
120
|
+
};
|
|
121
|
+
if (item.quantity != null) record.usage = {
|
|
122
|
+
value: item.quantity,
|
|
123
|
+
unit: item.unitType ?? "units"
|
|
124
|
+
};
|
|
125
|
+
records$1.push(record);
|
|
126
|
+
}
|
|
127
|
+
if (orgPlan) {
|
|
128
|
+
const pricePerSeat = planPricePerSeat(orgPlan.planName);
|
|
129
|
+
if (pricePerSeat > 0 && orgPlan.seats > 0) {
|
|
130
|
+
const dim = daysInMonth(day);
|
|
131
|
+
const dailyAmount = orgPlan.seats * pricePerSeat / dim;
|
|
132
|
+
records$1.push({
|
|
133
|
+
cloud: "github",
|
|
134
|
+
service: "seats",
|
|
135
|
+
date: day,
|
|
136
|
+
amount: Math.round(dailyAmount * 1e6) / 1e6,
|
|
137
|
+
currency: "USD",
|
|
138
|
+
usage: {
|
|
139
|
+
value: orgPlan.seats,
|
|
140
|
+
unit: "seats"
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return records$1;
|
|
146
|
+
}, 5)).flat();
|
|
147
|
+
this.cachedServices = [...new Set(records.map((r) => r.service))];
|
|
148
|
+
return records;
|
|
149
|
+
}
|
|
150
|
+
/** Fetch org plan info (plan name + seat count). Returns null on failure. */
|
|
151
|
+
async fetchOrgPlan(client, org, headers) {
|
|
152
|
+
try {
|
|
153
|
+
if (client.getOrgPlan) {
|
|
154
|
+
const resp = await client.getOrgPlan(`https://api.github.com/orgs/${org}`, { headers });
|
|
155
|
+
if (resp.status === 200 && resp.data.plan) return {
|
|
156
|
+
planName: resp.data.plan.name,
|
|
157
|
+
seats: resp.data.plan.seats
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
} catch {}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
async listServices() {
|
|
164
|
+
if (this.cachedServices) return this.cachedServices;
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
capabilities() {
|
|
168
|
+
return {
|
|
169
|
+
supportsAmount: true,
|
|
170
|
+
supportsUsage: true,
|
|
171
|
+
supportsTags: false,
|
|
172
|
+
supportsRegion: false,
|
|
173
|
+
supportsAccount: false,
|
|
174
|
+
supportsForecast: false
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
getClient() {
|
|
178
|
+
if (this.client) return this.client;
|
|
179
|
+
this.client = {
|
|
180
|
+
async get(url, options) {
|
|
181
|
+
const resp = await fetch(url, {
|
|
182
|
+
method: "GET",
|
|
183
|
+
headers: options.headers
|
|
184
|
+
});
|
|
185
|
+
const data = await resp.json();
|
|
186
|
+
return {
|
|
187
|
+
status: resp.status,
|
|
188
|
+
data
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
async getOrgPlan(url, options) {
|
|
192
|
+
const resp = await fetch(url, {
|
|
193
|
+
method: "GET",
|
|
194
|
+
headers: options.headers
|
|
195
|
+
});
|
|
196
|
+
const data = await resp.json();
|
|
197
|
+
return {
|
|
198
|
+
status: resp.status,
|
|
199
|
+
data
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
return this.client;
|
|
204
|
+
}
|
|
205
|
+
buildHeaders() {
|
|
206
|
+
return {
|
|
207
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
208
|
+
Accept: "application/json",
|
|
209
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
exports.GitHubCostAdapter = GitHubCostAdapter;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AdapterCapabilities, CostAdapter, CostRecord } from "@aigne/afs-cost-base";
|
|
2
|
+
|
|
3
|
+
//#region src/adapter.d.ts
|
|
4
|
+
/** Minimal HTTP client for GitHub Enhanced Billing Platform REST API */
|
|
5
|
+
interface GitHubHTTPClient {
|
|
6
|
+
get(url: string, options: {
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
}): Promise<GitHubBillingResponse>;
|
|
9
|
+
/** Fetch org plan/seat info. Optional — when absent, seat costs are not reported. */
|
|
10
|
+
getOrgPlan?(url: string, options: {
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
}): Promise<GitHubOrgPlanResponse>;
|
|
13
|
+
}
|
|
14
|
+
interface GitHubBillingResponse {
|
|
15
|
+
status: number;
|
|
16
|
+
data: {
|
|
17
|
+
usageItems?: GitHubUsageItem[];
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
interface GitHubOrgPlanResponse {
|
|
21
|
+
status: number;
|
|
22
|
+
data: {
|
|
23
|
+
plan?: {
|
|
24
|
+
name: string;
|
|
25
|
+
seats: number;
|
|
26
|
+
filled_seats: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface GitHubUsageItem {
|
|
31
|
+
date: string;
|
|
32
|
+
product: string;
|
|
33
|
+
sku: string;
|
|
34
|
+
quantity: number;
|
|
35
|
+
unitType: string;
|
|
36
|
+
pricePerUnit: number;
|
|
37
|
+
grossAmount: number;
|
|
38
|
+
discountAmount?: number;
|
|
39
|
+
netAmount: number;
|
|
40
|
+
organizationName?: string;
|
|
41
|
+
}
|
|
42
|
+
interface GitHubCostConfig {
|
|
43
|
+
token?: string;
|
|
44
|
+
org?: string;
|
|
45
|
+
}
|
|
46
|
+
declare class GitHubCostAdapter implements CostAdapter {
|
|
47
|
+
readonly cloud = "github";
|
|
48
|
+
private config;
|
|
49
|
+
private client?;
|
|
50
|
+
private cachedServices;
|
|
51
|
+
constructor(config?: GitHubCostConfig, client?: GitHubHTTPClient);
|
|
52
|
+
isAvailable(): Promise<boolean>;
|
|
53
|
+
fetchCosts(options: {
|
|
54
|
+
startDate: string;
|
|
55
|
+
endDate: string;
|
|
56
|
+
}): Promise<CostRecord[]>;
|
|
57
|
+
/** Fetch org plan info (plan name + seat count). Returns null on failure. */
|
|
58
|
+
private fetchOrgPlan;
|
|
59
|
+
listServices(): Promise<string[]>;
|
|
60
|
+
capabilities(): AdapterCapabilities;
|
|
61
|
+
private getClient;
|
|
62
|
+
private buildHeaders;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem };
|
|
66
|
+
//# sourceMappingURL=adapter.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.cts","names":[],"sources":["../src/adapter.ts"],"mappings":";;;;UAOiB,gBAAA;EACf,GAAA,CAAI,GAAA,UAAa,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IAA2B,OAAA,CAAQ,qBAAA;EAAnC;EAErC,UAAA,EACE,GAAA,UACA,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IACnB,OAAA,CAAQ,qBAAA;AAAA;AAAA,UAGI,qBAAA;EACf,MAAA;EACA,IAAA;IACE,UAAA,GAAa,eAAA;EAAA;AAAA;AAAA,UAIA,qBAAA;EACf,MAAA;EACA,IAAA;IACE,IAAA;MACE,IAAA;MACA,KAAA;MACA,YAAA;IAAA;EAAA;AAAA;AAAA,UAKW,eAAA;EACf,IAAA;EACA,OAAA;EACA,GAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,WAAA;EACA,cAAA;EACA,SAAA;EACA,gBAAA;AAAA;AAAA,UAkGe,gBAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,cAKW,iBAAA,YAA6B,WAAA;EAAA,SAC/B,KAAA;EAAA,QACD,MAAA;EAAA,QACA,MAAA;EAAA,QACA,cAAA;cAEI,MAAA,GAAQ,gBAAA,EAAuB,MAAA,GAAS,gBAAA;EAK9C,WAAA,CAAA,GAAe,OAAA;EAsBf,UAAA,CAAW,OAAA;IAAW,SAAA;IAAmB,OAAA;EAAA,IAAoB,OAAA,CAAQ,UAAA;;UAwF7D,YAAA;EAkBR,YAAA,CAAA,GAAgB,OAAA;EAKtB,YAAA,CAAA,GAAgB,mBAAA;EAAA,QAWR,SAAA;EAAA,QA8BA,YAAA;AAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AdapterCapabilities, CostAdapter, CostRecord } from "@aigne/afs-cost-base";
|
|
2
|
+
|
|
3
|
+
//#region src/adapter.d.ts
|
|
4
|
+
/** Minimal HTTP client for GitHub Enhanced Billing Platform REST API */
|
|
5
|
+
interface GitHubHTTPClient {
|
|
6
|
+
get(url: string, options: {
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
}): Promise<GitHubBillingResponse>;
|
|
9
|
+
/** Fetch org plan/seat info. Optional — when absent, seat costs are not reported. */
|
|
10
|
+
getOrgPlan?(url: string, options: {
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
}): Promise<GitHubOrgPlanResponse>;
|
|
13
|
+
}
|
|
14
|
+
interface GitHubBillingResponse {
|
|
15
|
+
status: number;
|
|
16
|
+
data: {
|
|
17
|
+
usageItems?: GitHubUsageItem[];
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
interface GitHubOrgPlanResponse {
|
|
21
|
+
status: number;
|
|
22
|
+
data: {
|
|
23
|
+
plan?: {
|
|
24
|
+
name: string;
|
|
25
|
+
seats: number;
|
|
26
|
+
filled_seats: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface GitHubUsageItem {
|
|
31
|
+
date: string;
|
|
32
|
+
product: string;
|
|
33
|
+
sku: string;
|
|
34
|
+
quantity: number;
|
|
35
|
+
unitType: string;
|
|
36
|
+
pricePerUnit: number;
|
|
37
|
+
grossAmount: number;
|
|
38
|
+
discountAmount?: number;
|
|
39
|
+
netAmount: number;
|
|
40
|
+
organizationName?: string;
|
|
41
|
+
}
|
|
42
|
+
interface GitHubCostConfig {
|
|
43
|
+
token?: string;
|
|
44
|
+
org?: string;
|
|
45
|
+
}
|
|
46
|
+
declare class GitHubCostAdapter implements CostAdapter {
|
|
47
|
+
readonly cloud = "github";
|
|
48
|
+
private config;
|
|
49
|
+
private client?;
|
|
50
|
+
private cachedServices;
|
|
51
|
+
constructor(config?: GitHubCostConfig, client?: GitHubHTTPClient);
|
|
52
|
+
isAvailable(): Promise<boolean>;
|
|
53
|
+
fetchCosts(options: {
|
|
54
|
+
startDate: string;
|
|
55
|
+
endDate: string;
|
|
56
|
+
}): Promise<CostRecord[]>;
|
|
57
|
+
/** Fetch org plan info (plan name + seat count). Returns null on failure. */
|
|
58
|
+
private fetchOrgPlan;
|
|
59
|
+
listServices(): Promise<string[]>;
|
|
60
|
+
capabilities(): AdapterCapabilities;
|
|
61
|
+
private getClient;
|
|
62
|
+
private buildHeaders;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem };
|
|
66
|
+
//# sourceMappingURL=adapter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.mts","names":[],"sources":["../src/adapter.ts"],"mappings":";;;;UAOiB,gBAAA;EACf,GAAA,CAAI,GAAA,UAAa,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IAA2B,OAAA,CAAQ,qBAAA;EAAnC;EAErC,UAAA,EACE,GAAA,UACA,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IACnB,OAAA,CAAQ,qBAAA;AAAA;AAAA,UAGI,qBAAA;EACf,MAAA;EACA,IAAA;IACE,UAAA,GAAa,eAAA;EAAA;AAAA;AAAA,UAIA,qBAAA;EACf,MAAA;EACA,IAAA;IACE,IAAA;MACE,IAAA;MACA,KAAA;MACA,YAAA;IAAA;EAAA;AAAA;AAAA,UAKW,eAAA;EACf,IAAA;EACA,OAAA;EACA,GAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,WAAA;EACA,cAAA;EACA,SAAA;EACA,gBAAA;AAAA;AAAA,UAkGe,gBAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,cAKW,iBAAA,YAA6B,WAAA;EAAA,SAC/B,KAAA;EAAA,QACD,MAAA;EAAA,QACA,MAAA;EAAA,QACA,cAAA;cAEI,MAAA,GAAQ,gBAAA,EAAuB,MAAA,GAAS,gBAAA;EAK9C,WAAA,CAAA,GAAe,OAAA;EAsBf,UAAA,CAAW,OAAA;IAAW,SAAA;IAAmB,OAAA;EAAA,IAAoB,OAAA,CAAQ,UAAA;;UAwF7D,YAAA;EAkBR,YAAA,CAAA,GAAgB,OAAA;EAKtB,YAAA,CAAA,GAAgB,mBAAA;EAAA,QAWR,SAAA;EAAA,QA8BA,YAAA;AAAA"}
|
package/dist/adapter.mjs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
//#region src/adapter.ts
|
|
2
|
+
const SERVICE_NAME_MAP = {
|
|
3
|
+
actions: "actions",
|
|
4
|
+
packages: "packages",
|
|
5
|
+
codespaces: "codespaces",
|
|
6
|
+
copilot: "copilot",
|
|
7
|
+
"shared-storage": "shared-storage",
|
|
8
|
+
"git-lfs-data": "git-lfs",
|
|
9
|
+
"actions-minutes": "actions",
|
|
10
|
+
"actions-storage": "actions-storage",
|
|
11
|
+
"codespaces-compute": "codespaces-compute",
|
|
12
|
+
"codespaces-prebuild": "codespaces-prebuild",
|
|
13
|
+
"copilot-business": "copilot",
|
|
14
|
+
"copilot-enterprise": "copilot"
|
|
15
|
+
};
|
|
16
|
+
function normalizeServiceName(product) {
|
|
17
|
+
if (!product) return "unknown";
|
|
18
|
+
const lower = product.toLowerCase();
|
|
19
|
+
if (SERVICE_NAME_MAP[lower]) return SERVICE_NAME_MAP[lower];
|
|
20
|
+
return lower.replace(/\s+/g, "-");
|
|
21
|
+
}
|
|
22
|
+
const PLAN_PRICE_MAP = {
|
|
23
|
+
free: 0,
|
|
24
|
+
team: 4,
|
|
25
|
+
"business-plus": 21,
|
|
26
|
+
enterprise: 21
|
|
27
|
+
};
|
|
28
|
+
function planPricePerSeat(planName) {
|
|
29
|
+
return PLAN_PRICE_MAP[planName.toLowerCase()] ?? 0;
|
|
30
|
+
}
|
|
31
|
+
/** Number of days in the month that contains `dateStr` (YYYY-MM-DD). */
|
|
32
|
+
function daysInMonth(dateStr) {
|
|
33
|
+
const { year, month } = parseDateParts(dateStr);
|
|
34
|
+
return new Date(year, month, 0).getDate();
|
|
35
|
+
}
|
|
36
|
+
/** Parse "YYYY-MM-DD" into { year, month, day } */
|
|
37
|
+
function parseDateParts(dateStr) {
|
|
38
|
+
const parts = dateStr.split("-").map(Number);
|
|
39
|
+
return {
|
|
40
|
+
year: parts[0] ?? 0,
|
|
41
|
+
month: parts[1] ?? 0,
|
|
42
|
+
day: parts[2] ?? 0
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Iterate over each day in [startDate, endDate) */
|
|
46
|
+
function* eachDay(startDate, endDate) {
|
|
47
|
+
const start = /* @__PURE__ */ new Date(`${startDate}T00:00:00Z`);
|
|
48
|
+
const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
|
|
49
|
+
const current = new Date(start);
|
|
50
|
+
while (current < end) {
|
|
51
|
+
yield current.toISOString().slice(0, 10);
|
|
52
|
+
current.setUTCDate(current.getUTCDate() + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Map over items with bounded concurrency. No external dependencies. */
|
|
56
|
+
async function pMap(items, fn, concurrency) {
|
|
57
|
+
const results = new Array(items.length);
|
|
58
|
+
let index = 0;
|
|
59
|
+
async function next() {
|
|
60
|
+
const i = index++;
|
|
61
|
+
if (i >= items.length) return;
|
|
62
|
+
results[i] = await fn(items[i]);
|
|
63
|
+
return next();
|
|
64
|
+
}
|
|
65
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => next()));
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
const BILLING_API_BASE = "https://api.github.com/orgs";
|
|
69
|
+
var GitHubCostAdapter = class {
|
|
70
|
+
cloud = "github";
|
|
71
|
+
config;
|
|
72
|
+
client;
|
|
73
|
+
cachedServices = null;
|
|
74
|
+
constructor(config = {}, client) {
|
|
75
|
+
this.config = config;
|
|
76
|
+
this.client = client;
|
|
77
|
+
}
|
|
78
|
+
async isAvailable() {
|
|
79
|
+
if (!this.config.token || !this.config.org) return false;
|
|
80
|
+
try {
|
|
81
|
+
const client = this.getClient();
|
|
82
|
+
if (!client) return false;
|
|
83
|
+
const { year, month, day } = parseDateParts((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
84
|
+
const url = `${BILLING_API_BASE}/${this.config.org}/settings/billing/usage?year=${year}&month=${month}&day=${day}`;
|
|
85
|
+
return (await client.get(url, { headers: this.buildHeaders() })).status === 200;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async fetchCosts(options) {
|
|
91
|
+
const client = this.getClient();
|
|
92
|
+
if (!client) throw new Error("GitHub HTTP client not available");
|
|
93
|
+
const org = this.config.org;
|
|
94
|
+
if (!org) throw new Error("Missing org in GitHub config");
|
|
95
|
+
const headers = this.buildHeaders();
|
|
96
|
+
const days = [...eachDay(options.startDate, options.endDate)];
|
|
97
|
+
const orgPlan = await this.fetchOrgPlan(client, org, headers);
|
|
98
|
+
const records = (await pMap(days, async (day) => {
|
|
99
|
+
const { year, month, day: d } = parseDateParts(day);
|
|
100
|
+
const url = `${BILLING_API_BASE}/${org}/settings/billing/usage?year=${year}&month=${month}&day=${d}`;
|
|
101
|
+
const response = await client.get(url, { headers });
|
|
102
|
+
if (response.status === 403) {
|
|
103
|
+
const msg = response.data?.message ?? "";
|
|
104
|
+
if (typeof msg === "string" && msg.toLowerCase().includes("rate limit")) throw new Error(`GitHub API rate limit exceeded. Retry after a few minutes.`);
|
|
105
|
+
throw new Error("Insufficient permissions: requires read:billing scope");
|
|
106
|
+
}
|
|
107
|
+
if (response.status === 404) throw new Error("Organization not found or not on Enhanced Billing Platform");
|
|
108
|
+
if (response.status !== 200) throw new Error(`GitHub API error: status ${response.status}`);
|
|
109
|
+
const items = response.data.usageItems ?? [];
|
|
110
|
+
const records$1 = [];
|
|
111
|
+
for (const item of items) {
|
|
112
|
+
const netAmount = item.netAmount ?? item.grossAmount - (item.discountAmount ?? 0);
|
|
113
|
+
const record = {
|
|
114
|
+
cloud: "github",
|
|
115
|
+
service: normalizeServiceName(item.product),
|
|
116
|
+
date: day,
|
|
117
|
+
amount: netAmount,
|
|
118
|
+
currency: "USD"
|
|
119
|
+
};
|
|
120
|
+
if (item.quantity != null) record.usage = {
|
|
121
|
+
value: item.quantity,
|
|
122
|
+
unit: item.unitType ?? "units"
|
|
123
|
+
};
|
|
124
|
+
records$1.push(record);
|
|
125
|
+
}
|
|
126
|
+
if (orgPlan) {
|
|
127
|
+
const pricePerSeat = planPricePerSeat(orgPlan.planName);
|
|
128
|
+
if (pricePerSeat > 0 && orgPlan.seats > 0) {
|
|
129
|
+
const dim = daysInMonth(day);
|
|
130
|
+
const dailyAmount = orgPlan.seats * pricePerSeat / dim;
|
|
131
|
+
records$1.push({
|
|
132
|
+
cloud: "github",
|
|
133
|
+
service: "seats",
|
|
134
|
+
date: day,
|
|
135
|
+
amount: Math.round(dailyAmount * 1e6) / 1e6,
|
|
136
|
+
currency: "USD",
|
|
137
|
+
usage: {
|
|
138
|
+
value: orgPlan.seats,
|
|
139
|
+
unit: "seats"
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return records$1;
|
|
145
|
+
}, 5)).flat();
|
|
146
|
+
this.cachedServices = [...new Set(records.map((r) => r.service))];
|
|
147
|
+
return records;
|
|
148
|
+
}
|
|
149
|
+
/** Fetch org plan info (plan name + seat count). Returns null on failure. */
|
|
150
|
+
async fetchOrgPlan(client, org, headers) {
|
|
151
|
+
try {
|
|
152
|
+
if (client.getOrgPlan) {
|
|
153
|
+
const resp = await client.getOrgPlan(`https://api.github.com/orgs/${org}`, { headers });
|
|
154
|
+
if (resp.status === 200 && resp.data.plan) return {
|
|
155
|
+
planName: resp.data.plan.name,
|
|
156
|
+
seats: resp.data.plan.seats
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
} catch {}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
async listServices() {
|
|
163
|
+
if (this.cachedServices) return this.cachedServices;
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
capabilities() {
|
|
167
|
+
return {
|
|
168
|
+
supportsAmount: true,
|
|
169
|
+
supportsUsage: true,
|
|
170
|
+
supportsTags: false,
|
|
171
|
+
supportsRegion: false,
|
|
172
|
+
supportsAccount: false,
|
|
173
|
+
supportsForecast: false
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
getClient() {
|
|
177
|
+
if (this.client) return this.client;
|
|
178
|
+
this.client = {
|
|
179
|
+
async get(url, options) {
|
|
180
|
+
const resp = await fetch(url, {
|
|
181
|
+
method: "GET",
|
|
182
|
+
headers: options.headers
|
|
183
|
+
});
|
|
184
|
+
const data = await resp.json();
|
|
185
|
+
return {
|
|
186
|
+
status: resp.status,
|
|
187
|
+
data
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
async getOrgPlan(url, options) {
|
|
191
|
+
const resp = await fetch(url, {
|
|
192
|
+
method: "GET",
|
|
193
|
+
headers: options.headers
|
|
194
|
+
});
|
|
195
|
+
const data = await resp.json();
|
|
196
|
+
return {
|
|
197
|
+
status: resp.status,
|
|
198
|
+
data
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
return this.client;
|
|
203
|
+
}
|
|
204
|
+
buildHeaders() {
|
|
205
|
+
return {
|
|
206
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
207
|
+
Accept: "application/json",
|
|
208
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
export { GitHubCostAdapter };
|
|
215
|
+
//# sourceMappingURL=adapter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.mjs","names":["records"],"sources":["../src/adapter.ts"],"sourcesContent":["import type { AdapterCapabilities, CostAdapter, CostRecord } from \"@aigne/afs-cost-base\";\n\n// ---------------------------------------------------------------------------\n// HTTP client interface (minimal subset for DI)\n// ---------------------------------------------------------------------------\n\n/** Minimal HTTP client for GitHub Enhanced Billing Platform REST API */\nexport interface GitHubHTTPClient {\n get(url: string, options: { headers: Record<string, string> }): Promise<GitHubBillingResponse>;\n /** Fetch org plan/seat info. Optional — when absent, seat costs are not reported. */\n getOrgPlan?(\n url: string,\n options: { headers: Record<string, string> },\n ): Promise<GitHubOrgPlanResponse>;\n}\n\nexport interface GitHubBillingResponse {\n status: number;\n data: {\n usageItems?: GitHubUsageItem[];\n };\n}\n\nexport interface GitHubOrgPlanResponse {\n status: number;\n data: {\n plan?: {\n name: string;\n seats: number;\n filled_seats: number;\n };\n };\n}\n\nexport interface GitHubUsageItem {\n date: string;\n product: string;\n sku: string;\n quantity: number;\n unitType: string;\n pricePerUnit: number;\n grossAmount: number;\n discountAmount?: number;\n netAmount: number;\n organizationName?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Service name normalization\n// ---------------------------------------------------------------------------\n\nconst SERVICE_NAME_MAP: Record<string, string> = {\n actions: \"actions\",\n packages: \"packages\",\n codespaces: \"codespaces\",\n copilot: \"copilot\",\n \"shared-storage\": \"shared-storage\",\n \"git-lfs-data\": \"git-lfs\",\n \"actions-minutes\": \"actions\",\n \"actions-storage\": \"actions-storage\",\n \"codespaces-compute\": \"codespaces-compute\",\n \"codespaces-prebuild\": \"codespaces-prebuild\",\n \"copilot-business\": \"copilot\",\n \"copilot-enterprise\": \"copilot\",\n};\n\nfunction normalizeServiceName(product: string): string {\n if (!product) return \"unknown\";\n const lower = product.toLowerCase();\n if (SERVICE_NAME_MAP[lower]) return SERVICE_NAME_MAP[lower];\n // Fallback: kebab-case\n return lower.replace(/\\s+/g, \"-\");\n}\n\n// ---------------------------------------------------------------------------\n// Plan pricing (USD per seat per month)\n// ---------------------------------------------------------------------------\n\nconst PLAN_PRICE_MAP: Record<string, number> = {\n free: 0,\n team: 4,\n \"business-plus\": 21,\n enterprise: 21,\n};\n\nfunction planPricePerSeat(planName: string): number {\n return PLAN_PRICE_MAP[planName.toLowerCase()] ?? 0;\n}\n\n/** Number of days in the month that contains `dateStr` (YYYY-MM-DD). */\nfunction daysInMonth(dateStr: string): number {\n const { year, month } = parseDateParts(dateStr);\n return new Date(year, month, 0).getDate(); // month is 1-based here, Date(y,m,0) gives last day of prev month\n}\n\n// ---------------------------------------------------------------------------\n// Date helpers\n// ---------------------------------------------------------------------------\n\n/** Parse \"YYYY-MM-DD\" into { year, month, day } */\nfunction parseDateParts(dateStr: string): { year: number; month: number; day: number } {\n const parts = dateStr.split(\"-\").map(Number);\n return { year: parts[0] ?? 0, month: parts[1] ?? 0, day: parts[2] ?? 0 };\n}\n\n/** Iterate over each day in [startDate, endDate) */\nfunction* eachDay(startDate: string, endDate: string): Generator<string> {\n const start = new Date(`${startDate}T00:00:00Z`);\n const end = new Date(`${endDate}T00:00:00Z`);\n const current = new Date(start);\n while (current < end) {\n yield current.toISOString().slice(0, 10);\n current.setUTCDate(current.getUTCDate() + 1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Concurrency helper\n// ---------------------------------------------------------------------------\n\n/** Map over items with bounded concurrency. No external dependencies. */\nasync function pMap<T, R>(\n items: T[],\n fn: (item: T) => Promise<R>,\n concurrency: number,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let index = 0;\n async function next(): Promise<void> {\n const i = index++;\n if (i >= items.length) return;\n results[i] = await fn(items[i]!);\n return next();\n }\n await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => next()));\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// GitHubCostAdapter\n// ---------------------------------------------------------------------------\n\nexport interface GitHubCostConfig {\n token?: string;\n org?: string;\n}\n\nconst BILLING_API_BASE = \"https://api.github.com/orgs\";\n\nexport class GitHubCostAdapter implements CostAdapter {\n readonly cloud = \"github\";\n private config: GitHubCostConfig;\n private client?: GitHubHTTPClient;\n private cachedServices: string[] | null = null;\n\n constructor(config: GitHubCostConfig = {}, client?: GitHubHTTPClient) {\n this.config = config;\n this.client = client;\n }\n\n async isAvailable(): Promise<boolean> {\n if (!this.config.token || !this.config.org) return false;\n\n try {\n const client = this.getClient();\n if (!client) return false;\n\n // Check org billing access with today's date\n const today = new Date().toISOString().slice(0, 10);\n const { year, month, day } = parseDateParts(today);\n const url = `${BILLING_API_BASE}/${this.config.org}/settings/billing/usage?year=${year}&month=${month}&day=${day}`;\n\n const response = await client.get(url, {\n headers: this.buildHeaders(),\n });\n\n return response.status === 200;\n } catch {\n return false;\n }\n }\n\n async fetchCosts(options: { startDate: string; endDate: string }): Promise<CostRecord[]> {\n const client = this.getClient();\n if (!client) throw new Error(\"GitHub HTTP client not available\");\n\n const org = this.config.org;\n if (!org) throw new Error(\"Missing org in GitHub config\");\n\n const headers = this.buildHeaders();\n const days = [...eachDay(options.startDate, options.endDate)];\n\n // Fetch org plan info for seat costs (non-blocking — skip on failure)\n const orgPlan = await this.fetchOrgPlan(client, org, headers);\n\n const dayResults = await pMap(\n days,\n async (day) => {\n const { year, month, day: d } = parseDateParts(day);\n const url = `${BILLING_API_BASE}/${org}/settings/billing/usage?year=${year}&month=${month}&day=${d}`;\n\n const response = await client.get(url, { headers });\n\n if (response.status === 403) {\n const msg = (response.data as Record<string, unknown>)?.message ?? \"\";\n if (typeof msg === \"string\" && msg.toLowerCase().includes(\"rate limit\")) {\n throw new Error(`GitHub API rate limit exceeded. Retry after a few minutes.`);\n }\n throw new Error(\"Insufficient permissions: requires read:billing scope\");\n }\n\n if (response.status === 404) {\n throw new Error(\"Organization not found or not on Enhanced Billing Platform\");\n }\n\n if (response.status !== 200) {\n throw new Error(`GitHub API error: status ${response.status}`);\n }\n\n const items = response.data.usageItems ?? [];\n const records: CostRecord[] = [];\n for (const item of items) {\n const netAmount = item.netAmount ?? item.grossAmount - (item.discountAmount ?? 0);\n\n const record: CostRecord = {\n cloud: \"github\",\n service: normalizeServiceName(item.product),\n date: day,\n amount: netAmount,\n currency: \"USD\",\n };\n\n if (item.quantity != null) {\n record.usage = {\n value: item.quantity,\n unit: item.unitType ?? \"units\",\n };\n }\n\n records.push(record);\n }\n\n // Add seat cost record for this day\n if (orgPlan) {\n const pricePerSeat = planPricePerSeat(orgPlan.planName);\n if (pricePerSeat > 0 && orgPlan.seats > 0) {\n const dim = daysInMonth(day);\n const dailyAmount = (orgPlan.seats * pricePerSeat) / dim;\n records.push({\n cloud: \"github\",\n service: \"seats\",\n date: day,\n amount: Math.round(dailyAmount * 1e6) / 1e6, // avoid floating-point noise\n currency: \"USD\",\n usage: { value: orgPlan.seats, unit: \"seats\" },\n });\n }\n }\n\n return records;\n },\n 5,\n );\n\n const records = dayResults.flat();\n this.cachedServices = [...new Set(records.map((r) => r.service))];\n return records;\n }\n\n /** Fetch org plan info (plan name + seat count). Returns null on failure. */\n private async fetchOrgPlan(\n client: GitHubHTTPClient,\n org: string,\n headers: Record<string, string>,\n ): Promise<{ planName: string; seats: number } | null> {\n try {\n if (client.getOrgPlan) {\n const resp = await client.getOrgPlan(`https://api.github.com/orgs/${org}`, { headers });\n if (resp.status === 200 && resp.data.plan) {\n return { planName: resp.data.plan.name, seats: resp.data.plan.seats };\n }\n }\n } catch {\n // Seat cost is best-effort; don't fail the entire fetch\n }\n return null;\n }\n\n async listServices(): Promise<string[]> {\n if (this.cachedServices) return this.cachedServices;\n return [];\n }\n\n capabilities(): AdapterCapabilities {\n return {\n supportsAmount: true,\n supportsUsage: true,\n supportsTags: false,\n supportsRegion: false,\n supportsAccount: false,\n supportsForecast: false,\n };\n }\n\n private getClient(): GitHubHTTPClient | undefined {\n if (this.client) return this.client;\n // Create a fetch-based client for production use\n this.client = {\n async get(\n url: string,\n options: { headers: Record<string, string> },\n ): Promise<GitHubBillingResponse> {\n const resp = await fetch(url, {\n method: \"GET\",\n headers: options.headers,\n });\n const data = (await resp.json()) as GitHubBillingResponse[\"data\"];\n return { status: resp.status, data };\n },\n async getOrgPlan(\n url: string,\n options: { headers: Record<string, string> },\n ): Promise<GitHubOrgPlanResponse> {\n const resp = await fetch(url, {\n method: \"GET\",\n headers: options.headers,\n });\n const data = (await resp.json()) as GitHubOrgPlanResponse[\"data\"];\n return { status: resp.status, data };\n },\n };\n return this.client;\n }\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.config.token}`,\n Accept: \"application/json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n };\n }\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const require_adapter = require('./adapter.cjs');
|
|
2
|
+
let _aigne_afs_utils_camelize = require("@aigne/afs/utils/camelize");
|
|
3
|
+
let _aigne_afs_cost_base = require("@aigne/afs-cost-base");
|
|
4
|
+
let zod = require("zod");
|
|
5
|
+
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
var AFSGithubCost = class AFSGithubCost extends _aigne_afs_cost_base.AFSCostBaseProvider {
|
|
8
|
+
cloudName = "github";
|
|
9
|
+
static schema() {
|
|
10
|
+
return zod.z.object({
|
|
11
|
+
token: zod.z.string().optional(),
|
|
12
|
+
org: zod.z.string().optional(),
|
|
13
|
+
name: zod.z.string().optional(),
|
|
14
|
+
description: zod.z.string().optional(),
|
|
15
|
+
accessMode: zod.z.enum(["readonly", "readwrite"]).optional(),
|
|
16
|
+
startDate: zod.z.string().optional(),
|
|
17
|
+
endDate: zod.z.string().optional(),
|
|
18
|
+
cacheTTL: zod.z.number().optional(),
|
|
19
|
+
dataDir: zod.z.string().optional()
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
static manifest() {
|
|
23
|
+
return {
|
|
24
|
+
name: "github-cost",
|
|
25
|
+
description: "GitHub billing data with by-service and by-date views, including seat costs.",
|
|
26
|
+
uriTemplate: "github-cost://{label+}",
|
|
27
|
+
category: "cloud",
|
|
28
|
+
schema: zod.z.object({ label: zod.z.string().optional() }),
|
|
29
|
+
tags: [
|
|
30
|
+
"cost",
|
|
31
|
+
"billing",
|
|
32
|
+
"github"
|
|
33
|
+
],
|
|
34
|
+
capabilityTags: [
|
|
35
|
+
"read-write",
|
|
36
|
+
"search",
|
|
37
|
+
"auth:token",
|
|
38
|
+
"remote"
|
|
39
|
+
],
|
|
40
|
+
security: {
|
|
41
|
+
riskLevel: "external",
|
|
42
|
+
resourceAccess: ["cloud-api", "internet"],
|
|
43
|
+
dataSensitivity: ["credentials"],
|
|
44
|
+
requires: ["cloud-credentials"]
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
static treeSchema() {
|
|
49
|
+
return {
|
|
50
|
+
operations: [
|
|
51
|
+
"list",
|
|
52
|
+
"read",
|
|
53
|
+
"write",
|
|
54
|
+
"search",
|
|
55
|
+
"stat",
|
|
56
|
+
"explain"
|
|
57
|
+
],
|
|
58
|
+
tree: {
|
|
59
|
+
"/": { kind: "github-cost:root" },
|
|
60
|
+
"/by-service": { kind: "github-cost:view" },
|
|
61
|
+
"/by-service/{service}": { kind: "github-cost:service-summary" },
|
|
62
|
+
"/by-date": { kind: "github-cost:view" },
|
|
63
|
+
"/by-date/{date}": { kind: "github-cost:date-summary" }
|
|
64
|
+
},
|
|
65
|
+
auth: {
|
|
66
|
+
type: "custom",
|
|
67
|
+
env: ["GITHUB_TOKEN"]
|
|
68
|
+
},
|
|
69
|
+
bestFor: [
|
|
70
|
+
"cost visibility",
|
|
71
|
+
"spend tracking",
|
|
72
|
+
"github billing",
|
|
73
|
+
"seat costs"
|
|
74
|
+
],
|
|
75
|
+
notFor: ["real-time data", "budget alerts"]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
static async load({ basePath, config } = {}) {
|
|
79
|
+
const camelized = (0, _aigne_afs_utils_camelize.camelize)(config ?? {});
|
|
80
|
+
return new AFSGithubCost({
|
|
81
|
+
...await AFSGithubCost.schema().parseAsync(camelized),
|
|
82
|
+
cwd: basePath
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
createAdapter(config) {
|
|
86
|
+
return new require_adapter.GitHubCostAdapter({
|
|
87
|
+
token: config.token,
|
|
88
|
+
org: config.org
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
exports.AFSGithubCost = AFSGithubCost;
|
|
95
|
+
exports.GitHubCostAdapter = require_adapter.GitHubCostAdapter;
|
|
96
|
+
Object.defineProperty(exports, 'MockCostAdapter', {
|
|
97
|
+
enumerable: true,
|
|
98
|
+
get: function () {
|
|
99
|
+
return _aigne_afs_cost_base.MockCostAdapter;
|
|
100
|
+
}
|
|
101
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem } from "./adapter.cjs";
|
|
2
|
+
import { AFSModuleLoadParams, ProviderManifest, ProviderTreeSchema } from "@aigne/afs";
|
|
3
|
+
import { AFSCostBaseProvider, AdapterCapabilities, CostAdapter, CostAdapter as CostAdapter$1, CostRecord, MockCostAdapter } from "@aigne/afs-cost-base";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/index.d.ts
|
|
7
|
+
declare class AFSGithubCost extends AFSCostBaseProvider {
|
|
8
|
+
readonly cloudName = "github";
|
|
9
|
+
static schema(): z.ZodObject<{
|
|
10
|
+
token: z.ZodOptional<z.ZodString>;
|
|
11
|
+
org: z.ZodOptional<z.ZodString>;
|
|
12
|
+
name: z.ZodOptional<z.ZodString>;
|
|
13
|
+
description: z.ZodOptional<z.ZodString>;
|
|
14
|
+
accessMode: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
readonly: "readonly";
|
|
16
|
+
readwrite: "readwrite";
|
|
17
|
+
}>>;
|
|
18
|
+
startDate: z.ZodOptional<z.ZodString>;
|
|
19
|
+
endDate: z.ZodOptional<z.ZodString>;
|
|
20
|
+
cacheTTL: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
dataDir: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
static manifest(): ProviderManifest;
|
|
24
|
+
static treeSchema(): ProviderTreeSchema;
|
|
25
|
+
static load({
|
|
26
|
+
basePath,
|
|
27
|
+
config
|
|
28
|
+
}?: AFSModuleLoadParams): Promise<AFSGithubCost>;
|
|
29
|
+
protected createAdapter(config: Record<string, unknown>): CostAdapter$1;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { AFSGithubCost, type AdapterCapabilities, type CostAdapter, type CostRecord, type GitHubBillingResponse, GitHubCostAdapter, type GitHubCostConfig, type GitHubHTTPClient, type GitHubOrgPlanResponse, type GitHubUsageItem, MockCostAdapter };
|
|
33
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;cA0Ba,aAAA,SAAsB,mBAAA;EAAA,SACxB,SAAA;EAAA,OAEF,MAAA,CAAA,GAAM,CAAA,CAAA,SAAA;;;;;;;;;;;;;;SAcN,QAAA,CAAA,GAAY,gBAAA;EAAA,OAkBZ,UAAA,CAAA,GAAc,kBAAA;EAAA,OAgBR,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAAwB,OAAA,CAAA,aAAA;EAAA,UAOtD,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,aAAA;AAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem } from "./adapter.mjs";
|
|
2
|
+
import { AFSCostBaseProvider, AdapterCapabilities, CostAdapter, CostAdapter as CostAdapter$1, CostRecord, MockCostAdapter } from "@aigne/afs-cost-base";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { AFSModuleLoadParams, ProviderManifest, ProviderTreeSchema } from "@aigne/afs";
|
|
5
|
+
|
|
6
|
+
//#region src/index.d.ts
|
|
7
|
+
declare class AFSGithubCost extends AFSCostBaseProvider {
|
|
8
|
+
readonly cloudName = "github";
|
|
9
|
+
static schema(): z.ZodObject<{
|
|
10
|
+
token: z.ZodOptional<z.ZodString>;
|
|
11
|
+
org: z.ZodOptional<z.ZodString>;
|
|
12
|
+
name: z.ZodOptional<z.ZodString>;
|
|
13
|
+
description: z.ZodOptional<z.ZodString>;
|
|
14
|
+
accessMode: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
readonly: "readonly";
|
|
16
|
+
readwrite: "readwrite";
|
|
17
|
+
}>>;
|
|
18
|
+
startDate: z.ZodOptional<z.ZodString>;
|
|
19
|
+
endDate: z.ZodOptional<z.ZodString>;
|
|
20
|
+
cacheTTL: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
dataDir: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
static manifest(): ProviderManifest;
|
|
24
|
+
static treeSchema(): ProviderTreeSchema;
|
|
25
|
+
static load({
|
|
26
|
+
basePath,
|
|
27
|
+
config
|
|
28
|
+
}?: AFSModuleLoadParams): Promise<AFSGithubCost>;
|
|
29
|
+
protected createAdapter(config: Record<string, unknown>): CostAdapter$1;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { AFSGithubCost, type AdapterCapabilities, type CostAdapter, type CostRecord, type GitHubBillingResponse, GitHubCostAdapter, type GitHubCostConfig, type GitHubHTTPClient, type GitHubOrgPlanResponse, type GitHubUsageItem, MockCostAdapter };
|
|
33
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;cA0Ba,aAAA,SAAsB,mBAAA;EAAA,SACxB,SAAA;EAAA,OAEF,MAAA,CAAA,GAAM,CAAA,CAAA,SAAA;;;;;;;;;;;;;;SAcN,QAAA,CAAA,GAAY,gBAAA;EAAA,OAkBZ,UAAA,CAAA,GAAc,kBAAA;EAAA,OAgBR,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAAwB,OAAA,CAAA,aAAA;EAAA,UAOtD,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,aAAA;AAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { GitHubCostAdapter } from "./adapter.mjs";
|
|
2
|
+
import { camelize } from "@aigne/afs/utils/camelize";
|
|
3
|
+
import { AFSCostBaseProvider, MockCostAdapter } from "@aigne/afs-cost-base";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
var AFSGithubCost = class AFSGithubCost extends AFSCostBaseProvider {
|
|
8
|
+
cloudName = "github";
|
|
9
|
+
static schema() {
|
|
10
|
+
return z.object({
|
|
11
|
+
token: z.string().optional(),
|
|
12
|
+
org: z.string().optional(),
|
|
13
|
+
name: z.string().optional(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
accessMode: z.enum(["readonly", "readwrite"]).optional(),
|
|
16
|
+
startDate: z.string().optional(),
|
|
17
|
+
endDate: z.string().optional(),
|
|
18
|
+
cacheTTL: z.number().optional(),
|
|
19
|
+
dataDir: z.string().optional()
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
static manifest() {
|
|
23
|
+
return {
|
|
24
|
+
name: "github-cost",
|
|
25
|
+
description: "GitHub billing data with by-service and by-date views, including seat costs.",
|
|
26
|
+
uriTemplate: "github-cost://{label+}",
|
|
27
|
+
category: "cloud",
|
|
28
|
+
schema: z.object({ label: z.string().optional() }),
|
|
29
|
+
tags: [
|
|
30
|
+
"cost",
|
|
31
|
+
"billing",
|
|
32
|
+
"github"
|
|
33
|
+
],
|
|
34
|
+
capabilityTags: [
|
|
35
|
+
"read-write",
|
|
36
|
+
"search",
|
|
37
|
+
"auth:token",
|
|
38
|
+
"remote"
|
|
39
|
+
],
|
|
40
|
+
security: {
|
|
41
|
+
riskLevel: "external",
|
|
42
|
+
resourceAccess: ["cloud-api", "internet"],
|
|
43
|
+
dataSensitivity: ["credentials"],
|
|
44
|
+
requires: ["cloud-credentials"]
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
static treeSchema() {
|
|
49
|
+
return {
|
|
50
|
+
operations: [
|
|
51
|
+
"list",
|
|
52
|
+
"read",
|
|
53
|
+
"write",
|
|
54
|
+
"search",
|
|
55
|
+
"stat",
|
|
56
|
+
"explain"
|
|
57
|
+
],
|
|
58
|
+
tree: {
|
|
59
|
+
"/": { kind: "github-cost:root" },
|
|
60
|
+
"/by-service": { kind: "github-cost:view" },
|
|
61
|
+
"/by-service/{service}": { kind: "github-cost:service-summary" },
|
|
62
|
+
"/by-date": { kind: "github-cost:view" },
|
|
63
|
+
"/by-date/{date}": { kind: "github-cost:date-summary" }
|
|
64
|
+
},
|
|
65
|
+
auth: {
|
|
66
|
+
type: "custom",
|
|
67
|
+
env: ["GITHUB_TOKEN"]
|
|
68
|
+
},
|
|
69
|
+
bestFor: [
|
|
70
|
+
"cost visibility",
|
|
71
|
+
"spend tracking",
|
|
72
|
+
"github billing",
|
|
73
|
+
"seat costs"
|
|
74
|
+
],
|
|
75
|
+
notFor: ["real-time data", "budget alerts"]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
static async load({ basePath, config } = {}) {
|
|
79
|
+
const camelized = camelize(config ?? {});
|
|
80
|
+
return new AFSGithubCost({
|
|
81
|
+
...await AFSGithubCost.schema().parseAsync(camelized),
|
|
82
|
+
cwd: basePath
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
createAdapter(config) {
|
|
86
|
+
return new GitHubCostAdapter({
|
|
87
|
+
token: config.token,
|
|
88
|
+
org: config.org
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { AFSGithubCost, GitHubCostAdapter, MockCostAdapter };
|
|
95
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { AFSModuleLoadParams, ProviderManifest, ProviderTreeSchema } from \"@aigne/afs\";\nimport { camelize } from \"@aigne/afs/utils/camelize\";\nimport {\n type AFSCostBaseOptions,\n AFSCostBaseProvider,\n type CostAdapter,\n} from \"@aigne/afs-cost-base\";\nimport { z } from \"zod\";\n\nimport { GitHubCostAdapter, type GitHubCostConfig } from \"./adapter.js\";\n\nexport type { AdapterCapabilities, CostAdapter, CostRecord } from \"@aigne/afs-cost-base\";\nexport { MockCostAdapter } from \"@aigne/afs-cost-base\";\nexport type {\n GitHubBillingResponse,\n GitHubCostConfig,\n GitHubHTTPClient,\n GitHubOrgPlanResponse,\n GitHubUsageItem,\n} from \"./adapter.js\";\nexport { GitHubCostAdapter } from \"./adapter.js\";\n\n// =============================================================================\n// AFSGithubCost Provider\n// =============================================================================\n\nexport class AFSGithubCost extends AFSCostBaseProvider {\n readonly cloudName = \"github\";\n\n static schema() {\n return z.object({\n token: z.string().optional(),\n org: z.string().optional(),\n name: z.string().optional(),\n description: z.string().optional(),\n accessMode: z.enum([\"readonly\", \"readwrite\"]).optional(),\n startDate: z.string().optional(),\n endDate: z.string().optional(),\n cacheTTL: z.number().optional(),\n dataDir: z.string().optional(),\n });\n }\n\n static manifest(): ProviderManifest {\n return {\n name: \"github-cost\",\n description: \"GitHub billing data with by-service and by-date views, including seat costs.\",\n uriTemplate: \"github-cost://{label+}\",\n category: \"cloud\",\n schema: z.object({ label: z.string().optional() }),\n tags: [\"cost\", \"billing\", \"github\"],\n capabilityTags: [\"read-write\", \"search\", \"auth:token\", \"remote\"],\n security: {\n riskLevel: \"external\",\n resourceAccess: [\"cloud-api\", \"internet\"],\n dataSensitivity: [\"credentials\"],\n requires: [\"cloud-credentials\"],\n },\n };\n }\n\n static treeSchema(): ProviderTreeSchema {\n return {\n operations: [\"list\", \"read\", \"write\", \"search\", \"stat\", \"explain\"],\n tree: {\n \"/\": { kind: \"github-cost:root\" },\n \"/by-service\": { kind: \"github-cost:view\" },\n \"/by-service/{service}\": { kind: \"github-cost:service-summary\" },\n \"/by-date\": { kind: \"github-cost:view\" },\n \"/by-date/{date}\": { kind: \"github-cost:date-summary\" },\n },\n auth: { type: \"custom\", env: [\"GITHUB_TOKEN\"] },\n bestFor: [\"cost visibility\", \"spend tracking\", \"github billing\", \"seat costs\"],\n notFor: [\"real-time data\", \"budget alerts\"],\n };\n }\n\n static async load({ basePath, config }: AFSModuleLoadParams = {}) {\n const camelized = camelize(config ?? {});\n const valid = await AFSGithubCost.schema().parseAsync(camelized);\n return new AFSGithubCost({ ...valid, cwd: basePath } as AFSCostBaseOptions &\n Record<string, unknown>);\n }\n\n protected createAdapter(config: Record<string, unknown>): CostAdapter {\n const adapterConfig: GitHubCostConfig = {\n token: config.token as string | undefined,\n org: config.org as string | undefined,\n };\n return new GitHubCostAdapter(adapterConfig);\n }\n}\n"],"mappings":";;;;;;AA0BA,IAAa,gBAAb,MAAa,sBAAsB,oBAAoB;CACrD,AAAS,YAAY;CAErB,OAAO,SAAS;AACd,SAAO,EAAE,OAAO;GACd,OAAO,EAAE,QAAQ,CAAC,UAAU;GAC5B,KAAK,EAAE,QAAQ,CAAC,UAAU;GAC1B,MAAM,EAAE,QAAQ,CAAC,UAAU;GAC3B,aAAa,EAAE,QAAQ,CAAC,UAAU;GAClC,YAAY,EAAE,KAAK,CAAC,YAAY,YAAY,CAAC,CAAC,UAAU;GACxD,WAAW,EAAE,QAAQ,CAAC,UAAU;GAChC,SAAS,EAAE,QAAQ,CAAC,UAAU;GAC9B,UAAU,EAAE,QAAQ,CAAC,UAAU;GAC/B,SAAS,EAAE,QAAQ,CAAC,UAAU;GAC/B,CAAC;;CAGJ,OAAO,WAA6B;AAClC,SAAO;GACL,MAAM;GACN,aAAa;GACb,aAAa;GACb,UAAU;GACV,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC;GAClD,MAAM;IAAC;IAAQ;IAAW;IAAS;GACnC,gBAAgB;IAAC;IAAc;IAAU;IAAc;IAAS;GAChE,UAAU;IACR,WAAW;IACX,gBAAgB,CAAC,aAAa,WAAW;IACzC,iBAAiB,CAAC,cAAc;IAChC,UAAU,CAAC,oBAAoB;IAChC;GACF;;CAGH,OAAO,aAAiC;AACtC,SAAO;GACL,YAAY;IAAC;IAAQ;IAAQ;IAAS;IAAU;IAAQ;IAAU;GAClE,MAAM;IACJ,KAAK,EAAE,MAAM,oBAAoB;IACjC,eAAe,EAAE,MAAM,oBAAoB;IAC3C,yBAAyB,EAAE,MAAM,+BAA+B;IAChE,YAAY,EAAE,MAAM,oBAAoB;IACxC,mBAAmB,EAAE,MAAM,4BAA4B;IACxD;GACD,MAAM;IAAE,MAAM;IAAU,KAAK,CAAC,eAAe;IAAE;GAC/C,SAAS;IAAC;IAAmB;IAAkB;IAAkB;IAAa;GAC9E,QAAQ,CAAC,kBAAkB,gBAAgB;GAC5C;;CAGH,aAAa,KAAK,EAAE,UAAU,WAAgC,EAAE,EAAE;EAChE,MAAM,YAAY,SAAS,UAAU,EAAE,CAAC;AAExC,SAAO,IAAI,cAAc;GAAE,GADb,MAAM,cAAc,QAAQ,CAAC,WAAW,UAAU;GAC3B,KAAK;GAAU,CAC1B;;CAG5B,AAAU,cAAc,QAA8C;AAKpE,SAAO,IAAI,kBAJ6B;GACtC,OAAO,OAAO;GACd,KAAK,OAAO;GACb,CAC0C"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aigne/afs-github-cost",
|
|
3
|
+
"version": "1.11.0-beta.12",
|
|
4
|
+
"description": "AIGNE AFS provider for GitHub billing data with seat costs",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"author": "Arcblock <blocklet@arcblock.io> https://github.com/arcblock",
|
|
10
|
+
"homepage": "https://github.com/arcblock/afs",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/arcblock/afs"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/arcblock/afs/issues"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"require": "./dist/index.cjs",
|
|
25
|
+
"import": "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./*": "./*"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"ufo": "^1.6.3",
|
|
37
|
+
"zod": "^4.0.0",
|
|
38
|
+
"@aigne/afs": "^1.11.0-beta.12",
|
|
39
|
+
"@aigne/afs-cost-base": "^1.11.0-beta.12"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/bun": "latest",
|
|
43
|
+
"npm-run-all": "^4.1.5",
|
|
44
|
+
"rimraf": "^6.1.2",
|
|
45
|
+
"tsdown": "0.20.0-beta.3",
|
|
46
|
+
"typescript": "5.9.2",
|
|
47
|
+
"@aigne/afs-testing": "1.11.0-beta.12",
|
|
48
|
+
"@aigne/scripts": "0.0.0",
|
|
49
|
+
"@aigne/typescript-config": "0.0.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsdown",
|
|
53
|
+
"check-types": "tsc --noEmit",
|
|
54
|
+
"clean": "rimraf dist coverage",
|
|
55
|
+
"test": "bun test",
|
|
56
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text"
|
|
57
|
+
}
|
|
58
|
+
}
|