@gpc-cli/cli 0.9.45 → 0.9.47
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/README.md +36 -15
- package/dist/anomalies-V3AFS4LD.js +66 -0
- package/dist/anomalies-V3AFS4LD.js.map +1 -0
- package/dist/{apps-J2446UDA.js → apps-FKD3ZG5X.js} +31 -35
- package/dist/apps-FKD3ZG5X.js.map +1 -0
- package/dist/{audit-N2CRHWUN.js → audit-JASSHRWN.js} +47 -62
- package/dist/audit-JASSHRWN.js.map +1 -0
- package/dist/{auth-XGSTT5G5.js → auth-OTA3SV3J.js} +145 -103
- package/dist/auth-OTA3SV3J.js.map +1 -0
- package/dist/bin.js +6 -4
- package/dist/bin.js.map +1 -1
- package/dist/bundle-F7MUVC5J.js +204 -0
- package/dist/bundle-F7MUVC5J.js.map +1 -0
- package/dist/{cache-SLNFRTI2.js → cache-XKPLZYEB.js} +4 -5
- package/dist/cache-XKPLZYEB.js.map +1 -0
- package/dist/changelog-QLDFG5TV.js +48 -0
- package/dist/changelog-QLDFG5TV.js.map +1 -0
- package/dist/{chunk-4O4D5SGL.js → chunk-3SJ6OXCZ.js} +4 -5
- package/dist/chunk-3SJ6OXCZ.js.map +1 -0
- package/dist/{chunk-U6ZTQ34I.js → chunk-BCBXQC7J.js} +45 -11
- package/dist/chunk-BCBXQC7J.js.map +1 -0
- package/dist/{chunk-AA577WVQ.js → chunk-NQH4G7BI.js} +9 -3
- package/dist/chunk-NQH4G7BI.js.map +1 -0
- package/dist/chunk-SLNJEAMK.js +23 -0
- package/dist/chunk-SLNJEAMK.js.map +1 -0
- package/dist/{chunk-SEVX56VN.js → chunk-WWVURXVO.js} +56 -49
- package/dist/chunk-WWVURXVO.js.map +1 -0
- package/dist/{chunk-NV75I5VP.js → chunk-YFUBD2XB.js} +10 -8
- package/dist/chunk-YFUBD2XB.js.map +1 -0
- package/dist/{config-BUXPDN7N.js → config-NY3TZGVS.js} +8 -5
- package/dist/config-NY3TZGVS.js.map +1 -0
- package/dist/{data-safety-Q7FTCEWU.js → data-safety-AFMD6MYI.js} +12 -27
- package/dist/data-safety-AFMD6MYI.js.map +1 -0
- package/dist/{device-tiers-MIOQEXYY.js → device-tiers-AQAMUQXI.js} +23 -38
- package/dist/device-tiers-AQAMUQXI.js.map +1 -0
- package/dist/diff-6EO4ID6W.js +91 -0
- package/dist/diff-6EO4ID6W.js.map +1 -0
- package/dist/{docs-7DUXIKA3.js → docs-4D2SJ4LY.js} +4 -3
- package/dist/docs-4D2SJ4LY.js.map +1 -0
- package/dist/doctor-QCCWG6Y3.js +708 -0
- package/dist/doctor-QCCWG6Y3.js.map +1 -0
- package/dist/{enterprise-7THXNBTC.js → enterprise-7PWXMSUN.js} +11 -21
- package/dist/enterprise-7PWXMSUN.js.map +1 -0
- package/dist/{external-transactions-2GWIMUVM.js → external-transactions-LCZALS3V.js} +12 -28
- package/dist/external-transactions-LCZALS3V.js.map +1 -0
- package/dist/{feedback-DPTO6DUT.js → feedback-CET2X67K.js} +4 -4
- package/dist/{games-BT777WUO.js → games-ZSNGEI7A.js} +17 -32
- package/dist/games-ZSNGEI7A.js.map +1 -0
- package/dist/{generated-apks-RJWTIX7L.js → generated-apks-RX2IUWSF.js} +30 -38
- package/dist/generated-apks-RX2IUWSF.js.map +1 -0
- package/dist/{grants-TKQJ3IER.js → grants-EBPECI26.js} +22 -40
- package/dist/grants-EBPECI26.js.map +1 -0
- package/dist/{iap-ICAEQLK5.js → iap-OUI5YYN4.js} +30 -51
- package/dist/iap-OUI5YYN4.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{init-JZ2THPMS.js → init-WSTQTJOD.js} +5 -4
- package/dist/init-WSTQTJOD.js.map +1 -0
- package/dist/{install-skills-OV4HVANW.js → install-skills-6QDUXI5F.js} +5 -6
- package/dist/{install-skills-OV4HVANW.js.map → install-skills-6QDUXI5F.js.map} +1 -1
- package/dist/{internal-sharing-3U2XFHA4.js → internal-sharing-ONNIWIAT.js} +3 -4
- package/dist/{internal-sharing-3U2XFHA4.js.map → internal-sharing-ONNIWIAT.js.map} +1 -1
- package/dist/{listings-77HZW4S5.js → listings-7SGQ4SRX.js} +118 -157
- package/dist/listings-7SGQ4SRX.js.map +1 -0
- package/dist/migrate-ZQCJGQQS.js +138 -0
- package/dist/migrate-ZQCJGQQS.js.map +1 -0
- package/dist/{one-time-products-LHZAXQES.js → one-time-products-MGZTU7OM.js} +65 -120
- package/dist/one-time-products-MGZTU7OM.js.map +1 -0
- package/dist/{preflight-H3HEBYQW.js → preflight-N7ZRG2JI.js} +58 -55
- package/dist/preflight-N7ZRG2JI.js.map +1 -0
- package/dist/{pricing-XQSDTTK5.js → pricing-JJZFICFL.js} +8 -8
- package/dist/{pricing-XQSDTTK5.js.map → pricing-JJZFICFL.js.map} +1 -1
- package/dist/{prompt-BSV22CQZ.js → prompt-GXC2JSLA.js} +2 -2
- package/dist/{publish-Q5ZKEKZ5.js → publish-JPTI4EBT.js} +34 -30
- package/dist/publish-JPTI4EBT.js.map +1 -0
- package/dist/{purchase-options-CKRN4VIW.js → purchase-options-KFWW4JW2.js} +16 -11
- package/dist/purchase-options-KFWW4JW2.js.map +1 -0
- package/dist/{purchases-43AKV6HG.js → purchases-Z3QBM3UO.js} +121 -194
- package/dist/purchases-Z3QBM3UO.js.map +1 -0
- package/dist/{quickstart-4HB62YEL.js → quickstart-Z5Y3FYJU.js} +5 -3
- package/dist/quickstart-Z5Y3FYJU.js.map +1 -0
- package/dist/{quota-UHIQQYOY.js → quota-MZRWYJGR.js} +5 -15
- package/dist/quota-MZRWYJGR.js.map +1 -0
- package/dist/{recovery-5EV2R476.js → recovery-YE3Z7NIN.js} +32 -61
- package/dist/recovery-YE3Z7NIN.js.map +1 -0
- package/dist/{releases-C2WC2K4E.js → releases-276W3BR7.js} +188 -187
- package/dist/releases-276W3BR7.js.map +1 -0
- package/dist/{reports-2YX3RDOS.js → reports-CIB2T3XT.js} +19 -21
- package/dist/reports-CIB2T3XT.js.map +1 -0
- package/dist/reviews-YCBBM656.js +199 -0
- package/dist/reviews-YCBBM656.js.map +1 -0
- package/dist/rtdn-LID2B7XZ.js +87 -0
- package/dist/rtdn-LID2B7XZ.js.map +1 -0
- package/dist/{status-WHGLODGV.js → status-6LH5W4FU.js} +105 -83
- package/dist/status-6LH5W4FU.js.map +1 -0
- package/dist/{subscriptions-CI3JH3VQ.js → subscriptions-DZP3Y7O7.js} +142 -232
- package/dist/subscriptions-DZP3Y7O7.js.map +1 -0
- package/dist/{testers-NZOFA3EF.js → testers-LSMBXCA2.js} +24 -44
- package/dist/testers-LSMBXCA2.js.map +1 -0
- package/dist/tracks-YHMO2A6B.js +98 -0
- package/dist/tracks-YHMO2A6B.js.map +1 -0
- package/dist/{train-CJJVLY4B.js → train-MDD2EBHS.js} +35 -55
- package/dist/train-MDD2EBHS.js.map +1 -0
- package/dist/{update-NAK6CMUX.js → update-XAO5EZHC.js} +30 -15
- package/dist/update-XAO5EZHC.js.map +1 -0
- package/dist/{users-2YTC4Q36.js → users-UKG7VIQH.js} +45 -67
- package/dist/users-UKG7VIQH.js.map +1 -0
- package/dist/{validate-UOVTM6L3.js → validate-QIYSA3N7.js} +8 -10
- package/dist/validate-QIYSA3N7.js.map +1 -0
- package/dist/{version-N64UBW7A.js → version-R3P4NHCF.js} +4 -4
- package/dist/{vitals-A4CS4MSS.js → vitals-PJEQUUAK.js} +174 -165
- package/dist/vitals-PJEQUUAK.js.map +1 -0
- package/package.json +6 -6
- package/dist/anomalies-NU2IN2GJ.js +0 -54
- package/dist/anomalies-NU2IN2GJ.js.map +0 -1
- package/dist/apps-J2446UDA.js.map +0 -1
- package/dist/audit-N2CRHWUN.js.map +0 -1
- package/dist/auth-XGSTT5G5.js.map +0 -1
- package/dist/bundle-F43TD2BQ.js +0 -218
- package/dist/bundle-F43TD2BQ.js.map +0 -1
- package/dist/cache-SLNFRTI2.js.map +0 -1
- package/dist/changelog-ZYD6W5IV.js +0 -53
- package/dist/changelog-ZYD6W5IV.js.map +0 -1
- package/dist/chunk-4O4D5SGL.js.map +0 -1
- package/dist/chunk-AA577WVQ.js.map +0 -1
- package/dist/chunk-FWKYRLKY.js +0 -19
- package/dist/chunk-FWKYRLKY.js.map +0 -1
- package/dist/chunk-NV75I5VP.js.map +0 -1
- package/dist/chunk-SEVX56VN.js.map +0 -1
- package/dist/chunk-U6ZTQ34I.js.map +0 -1
- package/dist/config-BUXPDN7N.js.map +0 -1
- package/dist/data-safety-Q7FTCEWU.js.map +0 -1
- package/dist/device-tiers-MIOQEXYY.js.map +0 -1
- package/dist/diff-V77SMKAQ.js +0 -96
- package/dist/diff-V77SMKAQ.js.map +0 -1
- package/dist/docs-7DUXIKA3.js.map +0 -1
- package/dist/doctor-3Z4ARPM2.js +0 -372
- package/dist/doctor-3Z4ARPM2.js.map +0 -1
- package/dist/enterprise-7THXNBTC.js.map +0 -1
- package/dist/external-transactions-2GWIMUVM.js.map +0 -1
- package/dist/games-BT777WUO.js.map +0 -1
- package/dist/generated-apks-RJWTIX7L.js.map +0 -1
- package/dist/grants-TKQJ3IER.js.map +0 -1
- package/dist/iap-ICAEQLK5.js.map +0 -1
- package/dist/init-JZ2THPMS.js.map +0 -1
- package/dist/listings-77HZW4S5.js.map +0 -1
- package/dist/migrate-SQT6RD6T.js +0 -143
- package/dist/migrate-SQT6RD6T.js.map +0 -1
- package/dist/one-time-products-LHZAXQES.js.map +0 -1
- package/dist/preflight-H3HEBYQW.js.map +0 -1
- package/dist/publish-Q5ZKEKZ5.js.map +0 -1
- package/dist/purchase-options-CKRN4VIW.js.map +0 -1
- package/dist/purchases-43AKV6HG.js.map +0 -1
- package/dist/quickstart-4HB62YEL.js.map +0 -1
- package/dist/quota-UHIQQYOY.js.map +0 -1
- package/dist/recovery-5EV2R476.js.map +0 -1
- package/dist/releases-C2WC2K4E.js.map +0 -1
- package/dist/reports-2YX3RDOS.js.map +0 -1
- package/dist/reviews-2CWOI5CV.js +0 -213
- package/dist/reviews-2CWOI5CV.js.map +0 -1
- package/dist/status-WHGLODGV.js.map +0 -1
- package/dist/subscriptions-CI3JH3VQ.js.map +0 -1
- package/dist/testers-NZOFA3EF.js.map +0 -1
- package/dist/tracks-NERFFEDT.js +0 -107
- package/dist/tracks-NERFFEDT.js.map +0 -1
- package/dist/train-CJJVLY4B.js.map +0 -1
- package/dist/update-NAK6CMUX.js.map +0 -1
- package/dist/users-2YTC4Q36.js.map +0 -1
- package/dist/validate-UOVTM6L3.js.map +0 -1
- package/dist/vitals-A4CS4MSS.js.map +0 -1
- /package/dist/{feedback-DPTO6DUT.js.map → feedback-CET2X67K.js.map} +0 -0
- /package/dist/{prompt-BSV22CQZ.js.map → prompt-GXC2JSLA.js.map} +0 -0
- /package/dist/{version-N64UBW7A.js.map → version-R3P4NHCF.js.map} +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getOutputFormat
|
|
4
|
+
} from "./chunk-ELXAK7GI.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/bundle.ts
|
|
7
|
+
import {
|
|
8
|
+
analyzeBundle,
|
|
9
|
+
compareBundles,
|
|
10
|
+
topFiles,
|
|
11
|
+
checkBundleSize,
|
|
12
|
+
formatOutput
|
|
13
|
+
} from "@gpc-cli/core";
|
|
14
|
+
function formatSize(bytes) {
|
|
15
|
+
const abs = Math.abs(bytes);
|
|
16
|
+
const sign = bytes < 0 ? "-" : "";
|
|
17
|
+
if (abs < 1024) return `${sign}${abs} B`;
|
|
18
|
+
if (abs < 1024 * 1024) return `${sign}${(abs / 1024).toFixed(1)} KB`;
|
|
19
|
+
return `${sign}${(abs / (1024 * 1024)).toFixed(2)} MB`;
|
|
20
|
+
}
|
|
21
|
+
function formatDelta(delta) {
|
|
22
|
+
const prefix = delta > 0 ? "+" : "";
|
|
23
|
+
return `${prefix}${formatSize(delta)}`;
|
|
24
|
+
}
|
|
25
|
+
function registerBundleCommands(program) {
|
|
26
|
+
const bundle = program.command("bundle").description("Analyze app bundles and APKs");
|
|
27
|
+
bundle.command("analyze <file>").description("Analyze size breakdown of an AAB or APK").option("--threshold <mb>", "Fail if compressed size exceeds threshold (MB)", parseFloat).option("--top <n>", "Show top N largest files", (v) => parseInt(v, 10)).option("--config <file>", "Check against .bundlesize.json thresholds").action(async (file, opts) => {
|
|
28
|
+
const format = getOutputFormat(program, await getConfig());
|
|
29
|
+
const analysis = await analyzeBundle(file);
|
|
30
|
+
if (format === "json") {
|
|
31
|
+
console.log(formatOutput(analysis, format));
|
|
32
|
+
} else if (format === "markdown") {
|
|
33
|
+
const moduleRows = analysis.modules.map((m) => ({
|
|
34
|
+
module: m.name,
|
|
35
|
+
compressed: formatSize(m.compressedSize),
|
|
36
|
+
uncompressed: formatSize(m.uncompressedSize),
|
|
37
|
+
entries: m.entries
|
|
38
|
+
}));
|
|
39
|
+
const categoryRows = analysis.categories.map((c) => ({
|
|
40
|
+
category: c.name,
|
|
41
|
+
compressed: formatSize(c.compressedSize),
|
|
42
|
+
uncompressed: formatSize(c.uncompressedSize),
|
|
43
|
+
entries: c.entries
|
|
44
|
+
}));
|
|
45
|
+
console.log(`## Bundle Analysis: \`${analysis.filePath}\``);
|
|
46
|
+
console.log();
|
|
47
|
+
console.log(`| Property | Value |`);
|
|
48
|
+
console.log(`| --- | --- |`);
|
|
49
|
+
console.log(`| Type | ${analysis.fileType.toUpperCase()} |`);
|
|
50
|
+
console.log(`| Total compressed | ${formatSize(analysis.totalCompressed)} |`);
|
|
51
|
+
console.log(`| Total uncompressed | ${formatSize(analysis.totalUncompressed)} |`);
|
|
52
|
+
console.log(`| Entries | ${analysis.entryCount} |`);
|
|
53
|
+
console.log();
|
|
54
|
+
console.log(`### Modules`);
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(formatOutput(moduleRows, "markdown"));
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(`### Categories`);
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(formatOutput(categoryRows, "markdown"));
|
|
61
|
+
} else {
|
|
62
|
+
console.log(`
|
|
63
|
+
File: ${analysis.filePath}`);
|
|
64
|
+
console.log(`Type: ${analysis.fileType.toUpperCase()}`);
|
|
65
|
+
console.log(`Total compressed: ${formatSize(analysis.totalCompressed)}`);
|
|
66
|
+
console.log(`Total uncompressed: ${formatSize(analysis.totalUncompressed)}`);
|
|
67
|
+
console.log(`Entries: ${analysis.entryCount}
|
|
68
|
+
`);
|
|
69
|
+
const moduleRows = analysis.modules.map((m) => ({
|
|
70
|
+
module: m.name,
|
|
71
|
+
compressed: formatSize(m.compressedSize),
|
|
72
|
+
uncompressed: formatSize(m.uncompressedSize),
|
|
73
|
+
entries: m.entries
|
|
74
|
+
}));
|
|
75
|
+
console.log("Modules:");
|
|
76
|
+
console.log(formatOutput(moduleRows, "table"));
|
|
77
|
+
const categoryRows = analysis.categories.map((c) => ({
|
|
78
|
+
category: c.name,
|
|
79
|
+
compressed: formatSize(c.compressedSize),
|
|
80
|
+
uncompressed: formatSize(c.uncompressedSize),
|
|
81
|
+
entries: c.entries
|
|
82
|
+
}));
|
|
83
|
+
console.log("\nCategories:");
|
|
84
|
+
console.log(formatOutput(categoryRows, "table"));
|
|
85
|
+
}
|
|
86
|
+
if (opts.top !== void 0) {
|
|
87
|
+
const largest = topFiles(analysis, opts.top);
|
|
88
|
+
const topRows = largest.map((e) => ({
|
|
89
|
+
path: e.path,
|
|
90
|
+
compressed: formatSize(e.compressedSize),
|
|
91
|
+
uncompressed: formatSize(e.uncompressedSize),
|
|
92
|
+
category: e.category
|
|
93
|
+
}));
|
|
94
|
+
console.log(`
|
|
95
|
+
Top ${opts.top} files:`);
|
|
96
|
+
console.log(formatOutput(topRows, format === "json" ? "table" : format));
|
|
97
|
+
}
|
|
98
|
+
if (opts.config) {
|
|
99
|
+
const check = await checkBundleSize(analysis, opts.config);
|
|
100
|
+
if (!check.passed) {
|
|
101
|
+
const violations = check.violations.map((v) => `${v.subject}: ${formatSize(v.actual)} > max ${formatSize(v.max)}`).join("; ");
|
|
102
|
+
const err = new Error(`Bundle size check failed: ${violations}`);
|
|
103
|
+
Object.assign(err, { code: "THRESHOLD_BREACH", exitCode: 6 });
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (opts.threshold !== void 0) {
|
|
108
|
+
const thresholdBytes = opts.threshold * 1024 * 1024;
|
|
109
|
+
if (analysis.totalCompressed > thresholdBytes) {
|
|
110
|
+
const err = new Error(`Threshold breached: ${formatSize(analysis.totalCompressed)} > ${opts.threshold} MB`);
|
|
111
|
+
Object.assign(err, { code: "THRESHOLD_BREACH", exitCode: 6 });
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
bundle.command("compare <file1> <file2>").description("Compare size differences between two bundles or APKs").action(async (file1, file2) => {
|
|
117
|
+
const format = getOutputFormat(program, await getConfig());
|
|
118
|
+
const [before, after] = await Promise.all([analyzeBundle(file1), analyzeBundle(file2)]);
|
|
119
|
+
const comparison = compareBundles(before, after);
|
|
120
|
+
if (format === "json") {
|
|
121
|
+
console.log(formatOutput(comparison, format));
|
|
122
|
+
} else if (format === "markdown") {
|
|
123
|
+
const sign = comparison.sizeDelta >= 0 ? "+" : "";
|
|
124
|
+
const moduleRows = comparison.moduleDeltas.filter((m) => m.delta !== 0).map((m) => ({
|
|
125
|
+
module: m.module,
|
|
126
|
+
before: formatSize(m.before),
|
|
127
|
+
after: formatSize(m.after),
|
|
128
|
+
delta: formatDelta(m.delta)
|
|
129
|
+
}));
|
|
130
|
+
const categoryRows = comparison.categoryDeltas.filter((c) => c.delta !== 0).map((c) => ({
|
|
131
|
+
category: c.category,
|
|
132
|
+
before: formatSize(c.before),
|
|
133
|
+
after: formatSize(c.after),
|
|
134
|
+
delta: formatDelta(c.delta)
|
|
135
|
+
}));
|
|
136
|
+
console.log(`## Bundle Comparison`);
|
|
137
|
+
console.log();
|
|
138
|
+
console.log(`| | Path | Size |`);
|
|
139
|
+
console.log(`| --- | --- | --- |`);
|
|
140
|
+
console.log(
|
|
141
|
+
`| Before | \`${comparison.before.path}\` | ${formatSize(comparison.before.totalCompressed)} |`
|
|
142
|
+
);
|
|
143
|
+
console.log(
|
|
144
|
+
`| After | \`${comparison.after.path}\` | ${formatSize(comparison.after.totalCompressed)} |`
|
|
145
|
+
);
|
|
146
|
+
console.log(
|
|
147
|
+
`| **Delta** | | **${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)** |`
|
|
148
|
+
);
|
|
149
|
+
if (moduleRows.length > 0) {
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(`### Module Changes`);
|
|
152
|
+
console.log();
|
|
153
|
+
console.log(formatOutput(moduleRows, "markdown"));
|
|
154
|
+
}
|
|
155
|
+
if (categoryRows.length > 0) {
|
|
156
|
+
console.log();
|
|
157
|
+
console.log(`### Category Changes`);
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(formatOutput(categoryRows, "markdown"));
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
const sign = comparison.sizeDelta >= 0 ? "+" : "";
|
|
163
|
+
console.log(
|
|
164
|
+
`
|
|
165
|
+
Before: ${comparison.before.path} (${formatSize(comparison.before.totalCompressed)})`
|
|
166
|
+
);
|
|
167
|
+
console.log(
|
|
168
|
+
`After: ${comparison.after.path} (${formatSize(comparison.after.totalCompressed)})`
|
|
169
|
+
);
|
|
170
|
+
console.log(
|
|
171
|
+
`Delta: ${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)
|
|
172
|
+
`
|
|
173
|
+
);
|
|
174
|
+
const moduleRows = comparison.moduleDeltas.filter((m) => m.delta !== 0).map((m) => ({
|
|
175
|
+
module: m.module,
|
|
176
|
+
before: formatSize(m.before),
|
|
177
|
+
after: formatSize(m.after),
|
|
178
|
+
delta: formatDelta(m.delta)
|
|
179
|
+
}));
|
|
180
|
+
if (moduleRows.length > 0) {
|
|
181
|
+
console.log("Module changes:");
|
|
182
|
+
console.log(formatOutput(moduleRows, "table"));
|
|
183
|
+
}
|
|
184
|
+
const categoryRows = comparison.categoryDeltas.filter((c) => c.delta !== 0).map((c) => ({
|
|
185
|
+
category: c.category,
|
|
186
|
+
before: formatSize(c.before),
|
|
187
|
+
after: formatSize(c.after),
|
|
188
|
+
delta: formatDelta(c.delta)
|
|
189
|
+
}));
|
|
190
|
+
if (categoryRows.length > 0) {
|
|
191
|
+
console.log("\nCategory changes:");
|
|
192
|
+
console.log(formatOutput(categoryRows, "table"));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function getConfig() {
|
|
198
|
+
const { loadConfig } = await import("@gpc-cli/config");
|
|
199
|
+
return loadConfig();
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
registerBundleCommands
|
|
203
|
+
};
|
|
204
|
+
//# sourceMappingURL=bundle-F7MUVC5J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/bundle.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport {\n analyzeBundle,\n compareBundles,\n topFiles,\n checkBundleSize,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nfunction formatSize(bytes: number): string {\n const abs = Math.abs(bytes);\n const sign = bytes < 0 ? \"-\" : \"\";\n if (abs < 1024) return `${sign}${abs} B`;\n if (abs < 1024 * 1024) return `${sign}${(abs / 1024).toFixed(1)} KB`;\n return `${sign}${(abs / (1024 * 1024)).toFixed(2)} MB`;\n}\n\nfunction formatDelta(delta: number): string {\n const prefix = delta > 0 ? \"+\" : \"\";\n return `${prefix}${formatSize(delta)}`;\n}\n\nexport function registerBundleCommands(program: Command): void {\n const bundle = program.command(\"bundle\").description(\"Analyze app bundles and APKs\");\n\n bundle\n .command(\"analyze <file>\")\n .description(\"Analyze size breakdown of an AAB or APK\")\n .option(\"--threshold <mb>\", \"Fail if compressed size exceeds threshold (MB)\", parseFloat)\n .option(\"--top <n>\", \"Show top N largest files\", (v) => parseInt(v, 10))\n .option(\"--config <file>\", \"Check against .bundlesize.json thresholds\")\n .action(async (file: string, opts: { threshold?: number; top?: number; config?: string }) => {\n const format = getOutputFormat(program, await getConfig());\n\n const analysis = await analyzeBundle(file);\n\n if (format === \"json\") {\n console.log(formatOutput(analysis, format));\n } else if (format === \"markdown\") {\n const moduleRows = analysis.modules.map((m) => ({\n module: m.name,\n compressed: formatSize(m.compressedSize),\n uncompressed: formatSize(m.uncompressedSize),\n entries: m.entries,\n }));\n const categoryRows = analysis.categories.map((c) => ({\n category: c.name,\n compressed: formatSize(c.compressedSize),\n uncompressed: formatSize(c.uncompressedSize),\n entries: c.entries,\n }));\n console.log(`## Bundle Analysis: \\`${analysis.filePath}\\``);\n console.log();\n console.log(`| Property | Value |`);\n console.log(`| --- | --- |`);\n console.log(`| Type | ${analysis.fileType.toUpperCase()} |`);\n console.log(`| Total compressed | ${formatSize(analysis.totalCompressed)} |`);\n console.log(`| Total uncompressed | ${formatSize(analysis.totalUncompressed)} |`);\n console.log(`| Entries | ${analysis.entryCount} |`);\n console.log();\n console.log(`### Modules`);\n console.log();\n console.log(formatOutput(moduleRows, \"markdown\"));\n console.log();\n console.log(`### Categories`);\n console.log();\n console.log(formatOutput(categoryRows, \"markdown\"));\n } else {\n console.log(`\\nFile: ${analysis.filePath}`);\n console.log(`Type: ${analysis.fileType.toUpperCase()}`);\n console.log(`Total compressed: ${formatSize(analysis.totalCompressed)}`);\n console.log(`Total uncompressed: ${formatSize(analysis.totalUncompressed)}`);\n console.log(`Entries: ${analysis.entryCount}\\n`);\n\n // Modules table\n const moduleRows = analysis.modules.map((m) => ({\n module: m.name,\n compressed: formatSize(m.compressedSize),\n uncompressed: formatSize(m.uncompressedSize),\n entries: m.entries,\n }));\n console.log(\"Modules:\");\n console.log(formatOutput(moduleRows, \"table\"));\n\n // Categories table\n const categoryRows = analysis.categories.map((c) => ({\n category: c.name,\n compressed: formatSize(c.compressedSize),\n uncompressed: formatSize(c.uncompressedSize),\n entries: c.entries,\n }));\n console.log(\"\\nCategories:\");\n console.log(formatOutput(categoryRows, \"table\"));\n }\n\n // Top N files\n if (opts.top !== undefined) {\n const largest = topFiles(analysis, opts.top);\n const topRows = largest.map((e) => ({\n path: e.path,\n compressed: formatSize(e.compressedSize),\n uncompressed: formatSize(e.uncompressedSize),\n category: e.category,\n }));\n console.log(`\\nTop ${opts.top} files:`);\n console.log(formatOutput(topRows, format === \"json\" ? \"table\" : format));\n }\n\n // .bundlesize.json config check\n if (opts.config) {\n const check = await checkBundleSize(analysis, opts.config);\n if (!check.passed) {\n const violations = check.violations.map((v) => `${v.subject}: ${formatSize(v.actual)} > max ${formatSize(v.max)}`).join(\"; \");\n const err = new Error(`Bundle size check failed: ${violations}`);\n Object.assign(err, { code: \"THRESHOLD_BREACH\", exitCode: 6 });\n throw err;\n }\n }\n\n // Legacy --threshold check\n if (opts.threshold !== undefined) {\n const thresholdBytes = opts.threshold * 1024 * 1024;\n if (analysis.totalCompressed > thresholdBytes) {\n const err = new Error(`Threshold breached: ${formatSize(analysis.totalCompressed)} > ${opts.threshold} MB`);\n Object.assign(err, { code: \"THRESHOLD_BREACH\", exitCode: 6 });\n throw err;\n }\n }\n });\n\n bundle\n .command(\"compare <file1> <file2>\")\n .description(\"Compare size differences between two bundles or APKs\")\n .action(async (file1: string, file2: string) => {\n const format = getOutputFormat(program, await getConfig());\n\n const [before, after] = await Promise.all([analyzeBundle(file1), analyzeBundle(file2)]);\n const comparison = compareBundles(before, after);\n\n if (format === \"json\") {\n console.log(formatOutput(comparison, format));\n } else if (format === \"markdown\") {\n const sign = comparison.sizeDelta >= 0 ? \"+\" : \"\";\n const moduleRows = comparison.moduleDeltas\n .filter((m) => m.delta !== 0)\n .map((m) => ({\n module: m.module,\n before: formatSize(m.before),\n after: formatSize(m.after),\n delta: formatDelta(m.delta),\n }));\n const categoryRows = comparison.categoryDeltas\n .filter((c) => c.delta !== 0)\n .map((c) => ({\n category: c.category,\n before: formatSize(c.before),\n after: formatSize(c.after),\n delta: formatDelta(c.delta),\n }));\n console.log(`## Bundle Comparison`);\n console.log();\n console.log(`| | Path | Size |`);\n console.log(`| --- | --- | --- |`);\n console.log(\n `| Before | \\`${comparison.before.path}\\` | ${formatSize(comparison.before.totalCompressed)} |`,\n );\n console.log(\n `| After | \\`${comparison.after.path}\\` | ${formatSize(comparison.after.totalCompressed)} |`,\n );\n console.log(\n `| **Delta** | | **${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)** |`,\n );\n if (moduleRows.length > 0) {\n console.log();\n console.log(`### Module Changes`);\n console.log();\n console.log(formatOutput(moduleRows, \"markdown\"));\n }\n if (categoryRows.length > 0) {\n console.log();\n console.log(`### Category Changes`);\n console.log();\n console.log(formatOutput(categoryRows, \"markdown\"));\n }\n } else {\n const sign = comparison.sizeDelta >= 0 ? \"+\" : \"\";\n console.log(\n `\\nBefore: ${comparison.before.path} (${formatSize(comparison.before.totalCompressed)})`,\n );\n console.log(\n `After: ${comparison.after.path} (${formatSize(comparison.after.totalCompressed)})`,\n );\n console.log(\n `Delta: ${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)\\n`,\n );\n\n // Module deltas\n const moduleRows = comparison.moduleDeltas\n .filter((m) => m.delta !== 0)\n .map((m) => ({\n module: m.module,\n before: formatSize(m.before),\n after: formatSize(m.after),\n delta: formatDelta(m.delta),\n }));\n if (moduleRows.length > 0) {\n console.log(\"Module changes:\");\n console.log(formatOutput(moduleRows, \"table\"));\n }\n\n // Category deltas\n const categoryRows = comparison.categoryDeltas\n .filter((c) => c.delta !== 0)\n .map((c) => ({\n category: c.category,\n before: formatSize(c.before),\n after: formatSize(c.after),\n delta: formatDelta(c.delta),\n }));\n if (categoryRows.length > 0) {\n console.log(\"\\nCategory changes:\");\n console.log(formatOutput(categoryRows, \"table\"));\n }\n }\n });\n}\n\nasync function getConfig() {\n const { loadConfig } = await import(\"@gpc-cli/config\");\n return loadConfig();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -74,10 +74,9 @@ function registerCacheCommand(program) {
|
|
|
74
74
|
if (opts.type) {
|
|
75
75
|
const matcher = FILE_TYPES[opts.type];
|
|
76
76
|
if (!matcher) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
process.exit(2);
|
|
77
|
+
const err = new Error(`Unknown cache type "${opts.type}".`);
|
|
78
|
+
Object.assign(err, { code: "USAGE_ERROR", exitCode: 2, suggestion: `Valid types: ${Object.keys(FILE_TYPES).join(", ")}` });
|
|
79
|
+
throw err;
|
|
81
80
|
}
|
|
82
81
|
toDelete = allFiles.filter((f) => matcher(f.name));
|
|
83
82
|
}
|
|
@@ -115,4 +114,4 @@ function registerCacheCommand(program) {
|
|
|
115
114
|
export {
|
|
116
115
|
registerCacheCommand
|
|
117
116
|
};
|
|
118
|
-
//# sourceMappingURL=cache-
|
|
117
|
+
//# sourceMappingURL=cache-XKPLZYEB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/cache.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { readdir, stat, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getCacheDir } from \"@gpc-cli/config\";\n\nconst FILE_TYPES: Record<string, (name: string) => boolean> = {\n status: (name) => name.startsWith(\"status-\") && name.endsWith(\".json\"),\n token: (name) => name === \"token-cache.json\",\n update: (name) => name === \"update-check.json\",\n};\n\nasync function getCacheFiles(\n cacheDir: string,\n): Promise<{ name: string; path: string; size: number; mtime: Date }[]> {\n let entries: string[];\n try {\n entries = await readdir(cacheDir);\n } catch {\n return [];\n }\n\n const files: { name: string; path: string; size: number; mtime: Date }[] = [];\n for (const entry of entries) {\n if (!entry.endsWith(\".json\")) continue;\n const filePath = join(cacheDir, entry);\n try {\n const info = await stat(filePath);\n if (info.isFile()) {\n files.push({ name: entry, path: filePath, size: info.size, mtime: info.mtime });\n }\n } catch {\n /* ignore */\n }\n }\n return files;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction fileType(name: string): string {\n for (const [type, matcher] of Object.entries(FILE_TYPES)) {\n if (matcher(name)) return type;\n }\n return \"other\";\n}\n\nexport function registerCacheCommand(program: Command): void {\n const cache = program.command(\"cache\").description(\"Manage local cache files\");\n\n cache\n .command(\"info\")\n .description(\"Show cache directory and total size\")\n .action(async () => {\n const cacheDir = getCacheDir();\n const files = await getCacheFiles(cacheDir);\n const totalSize = files.reduce((sum, f) => sum + f.size, 0);\n console.log(`Cache directory: ${cacheDir}`);\n console.log(`Files: ${files.length}`);\n console.log(`Total size: ${formatBytes(totalSize)}`);\n });\n\n cache\n .command(\"list\")\n .description(\"List all cache files with size and age\")\n .action(async () => {\n const cacheDir = getCacheDir();\n const files = await getCacheFiles(cacheDir);\n if (files.length === 0) {\n console.log(\"No cache files found.\");\n return;\n }\n const now = Date.now();\n for (const f of files) {\n const ageMs = now - f.mtime.getTime();\n const ageMins = Math.floor(ageMs / 60000);\n const ageStr =\n ageMins < 60\n ? `${ageMins}m ago`\n : ageMins < 1440\n ? `${Math.floor(ageMins / 60)}h ago`\n : `${Math.floor(ageMins / 1440)}d ago`;\n const type = fileType(f.name);\n console.log(` ${f.name} [${type}] ${formatBytes(f.size)} ${ageStr}`);\n }\n });\n\n cache\n .command(\"clear\")\n .description(\"Remove cache files\")\n .option(\"--force\", \"Skip confirmation prompt\")\n .option(\"--type <type>\", \"Remove only files of this type (status|token|update)\")\n .action(async (opts) => {\n const cacheDir = getCacheDir();\n const allFiles = await getCacheFiles(cacheDir);\n\n let toDelete = allFiles;\n if (opts.type) {\n const matcher = FILE_TYPES[opts.type];\n if (!matcher) {\n const err = new Error(`Unknown cache type \"${opts.type}\".`);\n Object.assign(err, { code: \"USAGE_ERROR\", exitCode: 2, suggestion: `Valid types: ${Object.keys(FILE_TYPES).join(\", \")}` });\n throw err;\n }\n toDelete = allFiles.filter((f) => matcher(f.name));\n }\n\n if (toDelete.length === 0) {\n console.log(\"No cache files to remove.\");\n return;\n }\n\n if (!opts.force) {\n console.log(`About to remove ${toDelete.length} file(s) from ${cacheDir}:`);\n for (const f of toDelete) {\n console.log(` ${f.name} (${formatBytes(f.size)})`);\n }\n const { createInterface } = await import(\"node:readline\");\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>((resolve) =>\n rl.question(\"Proceed? [y/N] \", resolve),\n );\n rl.close();\n if (answer.toLowerCase() !== \"y\") {\n console.log(\"Aborted.\");\n return;\n }\n }\n\n let removed = 0;\n for (const f of toDelete) {\n try {\n await unlink(f.path);\n removed++;\n } catch {\n /* ignore */\n }\n }\n console.log(`Removed ${removed} file(s).`);\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getOutputFormat
|
|
4
|
+
} from "./chunk-ELXAK7GI.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/changelog.ts
|
|
7
|
+
import { fetchChangelog, formatChangelogEntry } from "@gpc-cli/core";
|
|
8
|
+
import { formatOutput } from "@gpc-cli/core";
|
|
9
|
+
import { loadConfig } from "@gpc-cli/config";
|
|
10
|
+
function registerChangelogCommand(program) {
|
|
11
|
+
program.command("changelog").description("Show release history").option("-n, --limit <count>", "Number of releases to show", parseInt, 5).option("--tag <tag>", "Show a specific release (e.g., v0.9.43)").option("--all", "Show all releases").action(async (opts) => {
|
|
12
|
+
const config = await loadConfig();
|
|
13
|
+
const format = getOutputFormat(program, config);
|
|
14
|
+
const entries = await fetchChangelog({
|
|
15
|
+
limit: opts.all ? 100 : opts.limit,
|
|
16
|
+
version: opts.tag
|
|
17
|
+
});
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
console.log("No releases found.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (format === "json") {
|
|
23
|
+
console.log(formatOutput(entries, "json"));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (opts.tag || entries.length === 1) {
|
|
27
|
+
console.log(formatChangelogEntry(entries[0]));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const header = "VERSION DATE TITLE";
|
|
31
|
+
const separator = "\u2500".repeat(header.length);
|
|
32
|
+
console.log(header);
|
|
33
|
+
console.log(separator);
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const version = entry.version.padEnd(12);
|
|
36
|
+
const date = entry.date.padEnd(12);
|
|
37
|
+
console.log(`${version} ${date} ${entry.title}`);
|
|
38
|
+
}
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(
|
|
41
|
+
`Showing ${entries.length} releases. Use --tag <tag> for details, or --all for full history.`
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
registerChangelogCommand
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=changelog-QLDFG5TV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/changelog.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { fetchChangelog, formatChangelogEntry } from \"@gpc-cli/core\";\nimport { formatOutput } from \"@gpc-cli/core\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { getOutputFormat } from \"../format.js\";\n\nexport function registerChangelogCommand(program: Command): void {\n program\n .command(\"changelog\")\n .description(\"Show release history\")\n .option(\"-n, --limit <count>\", \"Number of releases to show\", parseInt, 5)\n .option(\"--tag <tag>\", \"Show a specific release (e.g., v0.9.43)\")\n .option(\"--all\", \"Show all releases\")\n .action(async (opts: { limit: number; tag?: string; all?: boolean }) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n const entries = await fetchChangelog({\n limit: opts.all ? 100 : opts.limit,\n version: opts.tag,\n });\n\n if (entries.length === 0) {\n console.log(\"No releases found.\");\n return;\n }\n\n if (format === \"json\") {\n console.log(formatOutput(entries, \"json\"));\n return;\n }\n\n // Single version — show full details\n if (opts.tag || entries.length === 1) {\n console.log(formatChangelogEntry(entries[0]!));\n return;\n }\n\n // Multiple versions — table summary\n const header = \"VERSION DATE TITLE\";\n const separator = \"─\".repeat(header.length);\n console.log(header);\n console.log(separator);\n for (const entry of entries) {\n const version = entry.version.padEnd(12);\n const date = entry.date.padEnd(12);\n console.log(`${version} ${date} ${entry.title}`);\n }\n console.log(\"\");\n console.log(\n `Showing ${entries.length} releases. Use --tag <tag> for details, or --all for full history.`,\n );\n });\n}\n"],"mappings":";;;;;;AACA,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAGpB,SAAS,yBAAyB,SAAwB;AAC/D,UACG,QAAQ,WAAW,EACnB,YAAY,sBAAsB,EAClC,OAAO,uBAAuB,8BAA8B,UAAU,CAAC,EACvE,OAAO,eAAe,yCAAyC,EAC/D,OAAO,SAAS,mBAAmB,EACnC,OAAO,OAAO,SAAyD;AACtE,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,UAAU,MAAM,eAAe;AAAA,MACnC,OAAO,KAAK,MAAM,MAAM,KAAK;AAAA,MAC7B,SAAS,KAAK;AAAA,IAChB,CAAC;AAED,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,oBAAoB;AAChC;AAAA,IACF;AAEA,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,SAAS,MAAM,CAAC;AACzC;AAAA,IACF;AAGA,QAAI,KAAK,OAAO,QAAQ,WAAW,GAAG;AACpC,cAAQ,IAAI,qBAAqB,QAAQ,CAAC,CAAE,CAAC;AAC7C;AAAA,IACF;AAGA,UAAM,SAAS;AACf,UAAM,YAAY,SAAI,OAAO,OAAO,MAAM;AAC1C,YAAQ,IAAI,MAAM;AAClB,YAAQ,IAAI,SAAS;AACrB,eAAW,SAAS,SAAS;AAC3B,YAAM,UAAU,MAAM,QAAQ,OAAO,EAAE;AACvC,YAAM,OAAO,MAAM,KAAK,OAAO,EAAE;AACjC,cAAQ,IAAI,GAAG,OAAO,IAAI,IAAI,IAAI,MAAM,KAAK,EAAE;AAAA,IACjD;AACA,YAAQ,IAAI,EAAE;AACd,YAAQ;AAAA,MACN,WAAW,QAAQ,MAAM;AAAA,IAC3B;AAAA,EACF,CAAC;AACL;","names":[]}
|
|
@@ -41,10 +41,9 @@ function writeCache(data) {
|
|
|
41
41
|
}
|
|
42
42
|
async function fetchLatestVersion() {
|
|
43
43
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
clearTimeout(timeout);
|
|
44
|
+
const response = await fetch(REGISTRY_URL, {
|
|
45
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
46
|
+
});
|
|
48
47
|
if (!response.ok) return null;
|
|
49
48
|
const body = await response.json();
|
|
50
49
|
if (typeof body.version !== "string") return null;
|
|
@@ -83,4 +82,4 @@ export {
|
|
|
83
82
|
checkForUpdate,
|
|
84
83
|
formatUpdateNotification
|
|
85
84
|
};
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
85
|
+
//# sourceMappingURL=chunk-3SJ6OXCZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/update-check.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { getCacheDir } from \"@gpc-cli/config\";\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string;\n updateAvailable: boolean;\n}\n\ninterface CacheData {\n latest: string;\n checkedAt: number;\n}\n\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst FETCH_TIMEOUT_MS = 3000;\nconst REGISTRY_URL = \"https://registry.npmjs.org/@gpc-cli/cli/latest\";\n\nfunction getCacheFilePath(): string {\n return join(getCacheDir(), \"update-check.json\");\n}\n\n/**\n * Compare two semver strings numerically.\n * Returns true if `b` is newer than `a`.\n */\nexport function isNewerVersion(current: string, latest: string): boolean {\n const a = current.split(\".\").map(Number);\n const b = latest.split(\".\").map(Number);\n for (let i = 0; i < Math.max(a.length, b.length); i++) {\n const av = a[i] ?? 0;\n const bv = b[i] ?? 0;\n if (bv > av) return true;\n if (bv < av) return false;\n }\n return false;\n}\n\nasync function readCache(): Promise<CacheData | null> {\n try {\n const raw = await readFile(getCacheFilePath(), \"utf-8\");\n const data = JSON.parse(raw) as CacheData;\n if (typeof data.latest === \"string\" && typeof data.checkedAt === \"number\") {\n return data;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(data: CacheData): void {\n const filePath = getCacheFilePath();\n const dir = join(filePath, \"..\");\n // Fire-and-forget: ignore write errors\n mkdir(dir, { recursive: true })\n .then(() => writeFile(filePath, JSON.stringify(data), \"utf-8\"))\n .catch(() => {});\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n try {\n const response = await fetch(REGISTRY_URL, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n\n if (!response.ok) return null;\n\n const body = (await response.json()) as { version?: string };\n if (typeof body.version !== \"string\") return null;\n\n return body.version;\n } catch {\n return null;\n }\n}\n\n/**\n * Check for a newer version of @gpc-cli/cli on npm.\n * Returns null if the check is skipped or fails.\n */\nexport async function checkForUpdate(currentVersion: string): Promise<UpdateCheckResult | null> {\n // Skip in non-interactive or CI environments\n if (process.env[\"GPC_NO_UPDATE_CHECK\"] === \"1\") return null;\n if (process.env[\"CI\"]) return null;\n if (!process.stdout.isTTY) return null;\n\n // Check cache first\n const cache = await readCache();\n if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {\n return {\n current: currentVersion,\n latest: cache.latest,\n updateAvailable: isNewerVersion(currentVersion, cache.latest),\n };\n }\n\n // Fetch from registry\n const latest = await fetchLatestVersion();\n if (!latest) return null;\n\n // Write cache (fire-and-forget)\n writeCache({ latest, checkedAt: Date.now() });\n\n return {\n current: currentVersion,\n latest,\n updateAvailable: isNewerVersion(currentVersion, latest),\n };\n}\n\n/**\n * Format a user-facing update notification string.\n */\nexport function formatUpdateNotification(result: UpdateCheckResult): string {\n return `Update available: ${result.current} \\u2192 ${result.latest} \\u2014 Run: gpc update`;\n}\n"],"mappings":";;;AAAA,SAAS,YAAY;AACrB,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,mBAAmB;AAa5B,IAAM,eAAe,KAAK,KAAK,KAAK;AACpC,IAAM,mBAAmB;AACzB,IAAM,eAAe;AAErB,SAAS,mBAA2B;AAClC,SAAO,KAAK,YAAY,GAAG,mBAAmB;AAChD;AAMO,SAAS,eAAe,SAAiB,QAAyB;AACvE,QAAM,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AACvC,QAAM,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI,MAAM;AACtC,WAAS,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK;AACrD,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,QAAI,KAAK,GAAI,QAAO;AACpB,QAAI,KAAK,GAAI,QAAO;AAAA,EACtB;AACA,SAAO;AACT;AAEA,eAAe,YAAuC;AACpD,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,iBAAiB,GAAG,OAAO;AACtD,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,QAAI,OAAO,KAAK,WAAW,YAAY,OAAO,KAAK,cAAc,UAAU;AACzE,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,WAAW,MAAuB;AACzC,QAAM,WAAW,iBAAiB;AAClC,QAAM,MAAM,KAAK,UAAU,IAAI;AAE/B,QAAM,KAAK,EAAE,WAAW,KAAK,CAAC,EAC3B,KAAK,MAAM,UAAU,UAAU,KAAK,UAAU,IAAI,GAAG,OAAO,CAAC,EAC7D,MAAM,MAAM;AAAA,EAAC,CAAC;AACnB;AAEA,eAAe,qBAA6C;AAC1D,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,cAAc;AAAA,MACzC,QAAQ,YAAY,QAAQ,gBAAgB;AAAA,IAC9C,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,QAAI,OAAO,KAAK,YAAY,SAAU,QAAO;AAE7C,WAAO,KAAK;AAAA,EACd,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,eAAe,gBAA2D;AAE9F,MAAI,QAAQ,IAAI,qBAAqB,MAAM,IAAK,QAAO;AACvD,MAAI,QAAQ,IAAI,IAAI,EAAG,QAAO;AAC9B,MAAI,CAAC,QAAQ,OAAO,MAAO,QAAO;AAGlC,QAAM,QAAQ,MAAM,UAAU;AAC9B,MAAI,SAAS,KAAK,IAAI,IAAI,MAAM,YAAY,cAAc;AACxD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,MAAM;AAAA,MACd,iBAAiB,eAAe,gBAAgB,MAAM,MAAM;AAAA,IAC9D;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,mBAAmB;AACxC,MAAI,CAAC,OAAQ,QAAO;AAGpB,aAAW,EAAE,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC;AAE5C,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,iBAAiB,eAAe,gBAAgB,MAAM;AAAA,EACxD;AACF;AAKO,SAAS,yBAAyB,QAAmC;AAC1E,SAAO,qBAAqB,OAAO,OAAO,WAAW,OAAO,MAAM;AACpE;","names":[]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
isNewerVersion
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3SJ6OXCZ.js";
|
|
5
5
|
|
|
6
6
|
// src/updater.ts
|
|
7
7
|
import { createWriteStream } from "fs";
|
|
8
|
-
import { rename, chmod, unlink, stat } from "fs/promises";
|
|
8
|
+
import { rename, chmod, unlink, stat, readdir } from "fs/promises";
|
|
9
9
|
import { realpathSync } from "fs";
|
|
10
10
|
import { join, dirname } from "path";
|
|
11
11
|
import { createHash } from "crypto";
|
|
@@ -50,14 +50,28 @@ function getCurrentBinaryPath() {
|
|
|
50
50
|
if (process.env["__GPC_BINARY"] === "1") return process.execPath;
|
|
51
51
|
return process.argv[1] ?? process.execPath;
|
|
52
52
|
}
|
|
53
|
-
function
|
|
53
|
+
function getGithubToken() {
|
|
54
|
+
return process.env["GPC_GITHUB_TOKEN"] || process.env["GITHUB_TOKEN"] || void 0;
|
|
55
|
+
}
|
|
56
|
+
function githubApiHeaders() {
|
|
54
57
|
const headers = {
|
|
55
58
|
"User-Agent": "gpc-cli",
|
|
56
59
|
Accept: "application/vnd.github+json",
|
|
57
60
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
58
61
|
};
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
const token = getGithubToken();
|
|
63
|
+
if (token) {
|
|
64
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
65
|
+
}
|
|
66
|
+
return headers;
|
|
67
|
+
}
|
|
68
|
+
function githubDownloadHeaders() {
|
|
69
|
+
const headers = {
|
|
70
|
+
"User-Agent": "gpc-cli"
|
|
71
|
+
};
|
|
72
|
+
const token = getGithubToken();
|
|
73
|
+
if (token) {
|
|
74
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
61
75
|
}
|
|
62
76
|
return headers;
|
|
63
77
|
}
|
|
@@ -65,7 +79,7 @@ async function fetchLatestRelease() {
|
|
|
65
79
|
let response;
|
|
66
80
|
try {
|
|
67
81
|
response = await fetch(GITHUB_API_URL, {
|
|
68
|
-
headers:
|
|
82
|
+
headers: githubApiHeaders(),
|
|
69
83
|
signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS)
|
|
70
84
|
});
|
|
71
85
|
} catch (err) {
|
|
@@ -75,10 +89,16 @@ async function fetchLatestRelease() {
|
|
|
75
89
|
exitCode: 5
|
|
76
90
|
});
|
|
77
91
|
}
|
|
78
|
-
if (response.status === 429) {
|
|
92
|
+
if (response.status === 429 || response.status === 403 && response.headers.get("x-ratelimit-remaining") === "0") {
|
|
79
93
|
throw Object.assign(
|
|
80
|
-
new Error(
|
|
81
|
-
|
|
94
|
+
new Error(
|
|
95
|
+
"GitHub API rate limit exceeded. Set GPC_GITHUB_TOKEN or GITHUB_TOKEN to increase the limit."
|
|
96
|
+
),
|
|
97
|
+
{
|
|
98
|
+
code: "UPDATE_RATE_LIMITED",
|
|
99
|
+
exitCode: 4,
|
|
100
|
+
suggestion: "export GPC_GITHUB_TOKEN=ghp_... (a personal access token with no scopes)"
|
|
101
|
+
}
|
|
82
102
|
);
|
|
83
103
|
}
|
|
84
104
|
if (!response.ok) {
|
|
@@ -94,7 +114,7 @@ async function fetchChecksums(release) {
|
|
|
94
114
|
if (!asset) return /* @__PURE__ */ new Map();
|
|
95
115
|
try {
|
|
96
116
|
const response = await fetch(asset.browser_download_url, {
|
|
97
|
-
headers:
|
|
117
|
+
headers: githubDownloadHeaders(),
|
|
98
118
|
signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS)
|
|
99
119
|
});
|
|
100
120
|
if (!response.ok) return /* @__PURE__ */ new Map();
|
|
@@ -202,6 +222,18 @@ async function sha256File(filePath) {
|
|
|
202
222
|
});
|
|
203
223
|
return hash.digest("hex");
|
|
204
224
|
}
|
|
225
|
+
function cleanupStaleUpdateFiles(binaryPath) {
|
|
226
|
+
const dir = dirname(binaryPath);
|
|
227
|
+
readdir(dir).then((files) => {
|
|
228
|
+
for (const f of files) {
|
|
229
|
+
if (f.startsWith(".gpc-old-") || f.startsWith(".gpc-update-")) {
|
|
230
|
+
unlink(join(dir, f)).catch(() => {
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}).catch(() => {
|
|
235
|
+
});
|
|
236
|
+
}
|
|
205
237
|
async function updateBinaryInPlace(assetUrl, expectedSha256, currentBinaryPath, options = {}) {
|
|
206
238
|
const dir = dirname(currentBinaryPath);
|
|
207
239
|
const tmpPath = join(dir, `.gpc-update-${process.pid}.tmp`);
|
|
@@ -210,6 +242,7 @@ async function updateBinaryInPlace(assetUrl, expectedSha256, currentBinaryPath,
|
|
|
210
242
|
let response;
|
|
211
243
|
try {
|
|
212
244
|
response = await fetch(assetUrl, {
|
|
245
|
+
headers: githubDownloadHeaders(),
|
|
213
246
|
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
214
247
|
});
|
|
215
248
|
} catch (err) {
|
|
@@ -307,6 +340,7 @@ export {
|
|
|
307
340
|
checkForUpdate,
|
|
308
341
|
updateViaNpm,
|
|
309
342
|
updateViaBrew,
|
|
343
|
+
cleanupStaleUpdateFiles,
|
|
310
344
|
updateBinaryInPlace
|
|
311
345
|
};
|
|
312
|
-
//# sourceMappingURL=chunk-
|
|
346
|
+
//# sourceMappingURL=chunk-BCBXQC7J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/updater.ts"],"sourcesContent":["/**\n * Self-update logic for `gpc update`.\n *\n * All functions here are pure / testable — no Commander.js dependency.\n * The command layer in commands/update.ts handles output and flags.\n */\n\nimport { createWriteStream } from \"node:fs\";\nimport { rename, chmod, unlink, stat, readdir } from \"node:fs/promises\";\nimport { realpathSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Readable, Transform } from \"node:stream\";\nimport { spawn } from \"node:child_process\";\nimport { isNewerVersion } from \"./update-check.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type InstallMethod = \"npm\" | \"homebrew\" | \"binary\" | \"unknown\";\n\nexport interface GithubAsset {\n name: string;\n browser_download_url: string;\n size: number;\n}\n\nexport interface GithubRelease {\n tag_name: string;\n html_url: string;\n assets: GithubAsset[];\n}\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string;\n latestTag: string;\n updateAvailable: boolean;\n installMethod: InstallMethod;\n release: GithubRelease;\n}\n\nconst GITHUB_API_URL = \"https://api.github.com/repos/yasserstudio/gpc/releases/latest\";\nconst GITHUB_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\n// ---------------------------------------------------------------------------\n// Install method detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect how gpc was installed.\n *\n * Priority:\n * 1. __GPC_BINARY env var (injected by esbuild at compile time) → \"binary\"\n * 2. npm_config_prefix env var → \"npm\"\n * 3. realpathSync(process.argv[1]) contains \"cellar\" or \"homebrew\" → \"homebrew\"\n * 4. realpathSync(process.argv[1]) contains \"node_modules\" → \"npm\"\n * 5. fallback → \"unknown\"\n *\n * Using realpathSync(process.argv[1]) instead of shelling to `which gpc`:\n * - No child process spawn\n * - Works on Windows without `where.exe`\n * - Resolves symlinks — critical for Intel Mac Homebrew where the bin path\n * is a symlink but the real path contains \"Cellar\"\n */\nexport function detectInstallMethod(): InstallMethod {\n // 1. Compiled binary — but Homebrew also distributes as compiled binary, check execPath\n if (process.env[\"__GPC_BINARY\"] === \"1\") {\n try {\n const resolved = realpathSync(process.execPath).toLowerCase();\n if (resolved.includes(\"cellar\") || resolved.includes(\"homebrew\")) return \"homebrew\";\n } catch {\n /* ignore */\n }\n return \"binary\";\n }\n\n // 2. npm global install\n if (process.env[\"npm_config_prefix\"]) return \"npm\";\n\n // 3. Resolve symlinks and inspect path\n try {\n const resolved = realpathSync(process.argv[1] ?? \"\").toLowerCase();\n if (resolved.includes(\"cellar\") || resolved.includes(\"homebrew\")) return \"homebrew\";\n if (resolved.includes(\"node_modules\")) return \"npm\";\n } catch {\n // realpathSync can throw if the path doesn't exist — fall through to unknown\n }\n\n return \"unknown\";\n}\n\n// ---------------------------------------------------------------------------\n// Platform asset mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the GitHub release asset name for the current platform/arch,\n * matching the names produced by scripts/build-binary.ts TARGETS map.\n */\nexport function getPlatformAsset(): string | null {\n const arch = process.arch === \"arm64\" ? \"arm64\" : \"x64\";\n switch (process.platform) {\n case \"darwin\":\n return `gpc-darwin-${arch}`;\n case \"linux\":\n return `gpc-linux-${arch}`;\n case \"win32\":\n return \"gpc-windows-x64.exe\";\n default:\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Current binary path\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the path of the currently running gpc binary.\n * For compiled binaries, process.execPath IS the binary.\n * For npm/dev installs, process.argv[1] is the script entrypoint.\n */\nexport function getCurrentBinaryPath(): string {\n if (process.env[\"__GPC_BINARY\"] === \"1\") return process.execPath;\n return process.argv[1] ?? process.execPath;\n}\n\n// ---------------------------------------------------------------------------\n// GitHub Releases API\n// ---------------------------------------------------------------------------\n\nfunction getGithubToken(): string | undefined {\n return process.env[\"GPC_GITHUB_TOKEN\"] || process.env[\"GITHUB_TOKEN\"] || undefined;\n}\n\nfunction githubApiHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"User-Agent\": \"gpc-cli\",\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n };\n const token = getGithubToken();\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n return headers;\n}\n\nfunction githubDownloadHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"User-Agent\": \"gpc-cli\",\n };\n const token = getGithubToken();\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n return headers;\n}\n\nexport async function fetchLatestRelease(): Promise<GithubRelease> {\n let response: Response;\n try {\n response = await fetch(GITHUB_API_URL, {\n headers: githubApiHeaders(),\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(`Network error checking for updates: ${msg}`), {\n code: \"NETWORK_ERROR\",\n exitCode: 5,\n });\n }\n\n if (response.status === 429 ||\n (response.status === 403 && response.headers.get(\"x-ratelimit-remaining\") === \"0\")) {\n throw Object.assign(\n new Error(\n \"GitHub API rate limit exceeded. Set GPC_GITHUB_TOKEN or GITHUB_TOKEN to increase the limit.\",\n ),\n {\n code: \"UPDATE_RATE_LIMITED\",\n exitCode: 4,\n suggestion: \"export GPC_GITHUB_TOKEN=ghp_... (a personal access token with no scopes)\",\n },\n );\n }\n\n if (!response.ok) {\n throw Object.assign(new Error(`GitHub API returned HTTP ${response.status}`), {\n code: \"UPDATE_API_ERROR\",\n exitCode: 4,\n });\n }\n\n return (await response.json()) as GithubRelease;\n}\n\n/**\n * Fetch and parse checksums.txt from the release assets.\n * Returns a Map of filename → lowercase sha256 hex.\n * Returns an empty Map if the asset is missing or the fetch fails.\n */\nexport async function fetchChecksums(release: GithubRelease): Promise<Map<string, string>> {\n const asset = release.assets.find((a) => a.name === \"checksums.txt\");\n if (!asset) return new Map();\n\n try {\n const response = await fetch(asset.browser_download_url, {\n headers: githubDownloadHeaders(),\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n });\n if (!response.ok) return new Map();\n\n const map = new Map<string, string>();\n for (const line of (await response.text()).split(\"\\n\")) {\n const parts = line.trim().split(/\\s+/);\n const hash = parts[0];\n const name = parts[1];\n if (hash && name) map.set(name, hash.toLowerCase());\n }\n return map;\n } catch {\n return new Map();\n }\n}\n\n// ---------------------------------------------------------------------------\n// High-level update check\n// ---------------------------------------------------------------------------\n\nexport async function checkForUpdate(currentVersion: string): Promise<UpdateCheckResult> {\n const release = await fetchLatestRelease();\n const latest = release.tag_name.replace(/^v/, \"\");\n return {\n current: currentVersion,\n latest,\n latestTag: release.tag_name,\n updateAvailable: isNewerVersion(currentVersion, latest),\n installMethod: detectInstallMethod(),\n release,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Update execution paths\n// ---------------------------------------------------------------------------\n\nexport async function updateViaNpm(options: { silent?: boolean } = {}): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"npm\", [\"install\", \"-g\", \"@gpc-cli/cli@latest\"], {\n // In silent (JSON) mode, redirect npm's stdout to stderr so it doesn't\n // pollute the machine-readable JSON that gpc writes to stdout.\n stdio: options.silent ? [\"inherit\", process.stderr, process.stderr] : \"inherit\",\n shell: false,\n });\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n Object.assign(new Error(`npm exited with code ${code}`), {\n code: \"UPDATE_NPM_FAILED\",\n exitCode: 1,\n suggestion: \"Run manually: npm install -g @gpc-cli/cli@latest\",\n }),\n );\n }\n });\n proc.on(\"error\", (err) => {\n reject(\n Object.assign(new Error(`Failed to run npm: ${err.message}`), {\n code: \"UPDATE_NPM_SPAWN_FAILED\",\n exitCode: 1,\n suggestion: \"Ensure npm is in your PATH\",\n }),\n );\n });\n });\n}\n\nexport async function updateViaBrew(options: { silent?: boolean } = {}): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"brew\", [\"upgrade\", \"yasserstudio/tap/gpc\"], {\n // In silent (JSON) mode, redirect brew's stdout to stderr so it doesn't\n // pollute the machine-readable JSON that gpc writes to stdout.\n stdio: options.silent ? [\"inherit\", process.stderr, process.stderr] : \"inherit\",\n shell: false,\n });\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n Object.assign(new Error(`brew exited with code ${code}`), {\n code: \"UPDATE_BREW_FAILED\",\n exitCode: 1,\n suggestion: \"Run manually: brew upgrade yasserstudio/tap/gpc\",\n }),\n );\n }\n });\n proc.on(\"error\", (err) => {\n reject(\n Object.assign(new Error(`Failed to run brew: ${err.message}`), {\n code: \"UPDATE_BREW_SPAWN_FAILED\",\n exitCode: 1,\n suggestion: \"Ensure Homebrew is installed: https://brew.sh\",\n }),\n );\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Binary in-place replace\n// ---------------------------------------------------------------------------\n\nfunction isPermissionError(err: unknown): boolean {\n return err instanceof Error && \"code\" in err && (err.code === \"EACCES\" || err.code === \"EPERM\");\n}\n\nasync function sha256File(filePath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const { size } = await stat(filePath);\n if (size === 0) return hash.digest(\"hex\");\n\n // Read in 64 KB chunks\n const { createReadStream } = await import(\"node:fs\");\n const stream = createReadStream(filePath);\n await new Promise<void>((resolve, reject) => {\n stream.on(\"data\", (chunk: Buffer) => hash.update(chunk));\n stream.on(\"end\", resolve);\n stream.on(\"error\", reject);\n });\n return hash.digest(\"hex\");\n}\n\n/**\n * Clean up stale `.gpc-old-*` and `.gpc-update-*.tmp` files left by\n * previous update attempts (e.g. Windows EBUSY on unlink).\n * Fire-and-forget — errors are silently ignored.\n */\nexport function cleanupStaleUpdateFiles(binaryPath: string): void {\n const dir = dirname(binaryPath);\n readdir(dir)\n .then((files) => {\n for (const f of files) {\n if (f.startsWith(\".gpc-old-\") || f.startsWith(\".gpc-update-\")) {\n unlink(join(dir, f)).catch(() => {});\n }\n }\n })\n .catch(() => {});\n}\n\n/**\n * Download a new binary and atomically replace the current one.\n *\n * macOS/Linux: rename(tmp, current) — safe because open files can be replaced\n * Windows: rename(current, .old) then rename(tmp, current) — avoids EBUSY\n * because Windows locks running executables\n */\nexport async function updateBinaryInPlace(\n assetUrl: string,\n expectedSha256: string,\n currentBinaryPath: string,\n options: { onProgress?: (downloaded: number, total: number) => void } = {},\n): Promise<void> {\n const dir = dirname(currentBinaryPath);\n const tmpPath = join(dir, `.gpc-update-${process.pid}.tmp`);\n const oldPath = join(dir, `.gpc-old-${process.pid}`);\n\n try {\n // 1. Download\n let response: Response;\n try {\n response = await fetch(assetUrl, {\n headers: githubDownloadHeaders(),\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(`Download failed: ${msg}`), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 5,\n });\n }\n\n if (!response.ok) {\n throw Object.assign(new Error(`Download failed: HTTP ${response.status}`), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 4,\n });\n }\n if (!response.body) {\n throw Object.assign(new Error(\"Empty response body\"), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 4,\n });\n }\n\n const contentLength = response.headers.get(\"content-length\");\n const total = contentLength ? parseInt(contentLength, 10) : 0;\n let downloaded = 0;\n\n const dest = createWriteStream(tmpPath);\n const { onProgress } = options;\n\n if (onProgress) {\n const tracker = new Transform({\n transform(chunk: Buffer, _enc, cb) {\n downloaded += chunk.length;\n onProgress(downloaded, total);\n cb(null, chunk);\n },\n });\n await pipeline(\n Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]),\n tracker,\n dest,\n );\n } else {\n await pipeline(\n Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]),\n dest,\n );\n }\n\n // 2. Verify checksum (skip if no checksum available)\n if (expectedSha256) {\n const actual = await sha256File(tmpPath);\n if (actual !== expectedSha256.toLowerCase()) {\n throw Object.assign(\n new Error(`Checksum mismatch — expected ${expectedSha256}, got ${actual}`),\n {\n code: \"UPDATE_CHECKSUM_MISMATCH\",\n exitCode: 1,\n suggestion: \"The download may be corrupt. Try again.\",\n },\n );\n }\n }\n\n // 3. Set executable bit (no-op on Windows)\n if (process.platform !== \"win32\") {\n await chmod(tmpPath, 0o755);\n }\n\n // 4. Atomic replace\n if (process.platform === \"win32\") {\n // Windows locks running executables, so we rename the current binary\n // out of the way first, then move the new one into place\n await rename(currentBinaryPath, oldPath);\n try {\n await rename(tmpPath, currentBinaryPath);\n } catch (renameErr) {\n // Roll back — restore original binary\n await rename(oldPath, currentBinaryPath).catch(() => {});\n throw renameErr;\n }\n // Delete the old binary in the background (best-effort)\n unlink(oldPath).catch(() => {});\n } else {\n await rename(tmpPath, currentBinaryPath);\n }\n } catch (err) {\n // Clean up temp file on any error\n await unlink(tmpPath).catch(() => {});\n\n if (isPermissionError(err)) {\n throw Object.assign(new Error(`Permission denied replacing ${currentBinaryPath}`), {\n code: \"UPDATE_PERMISSION_DENIED\",\n exitCode: 1,\n suggestion: `Run with elevated permissions: sudo gpc update`,\n });\n }\n throw err;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -6,8 +6,14 @@ import { createApiClient, createReportingClient } from "@gpc-cli/api";
|
|
|
6
6
|
function resolvePackageName(packageArg, config) {
|
|
7
7
|
const name = packageArg || config.app || process.env["GPC_APP"];
|
|
8
8
|
if (!name) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
throw Object.assign(
|
|
10
|
+
new Error("No package name"),
|
|
11
|
+
{
|
|
12
|
+
code: "MISSING_PACKAGE_NAME",
|
|
13
|
+
exitCode: 2,
|
|
14
|
+
suggestion: "Use --app <package> or gpc config set app <package>"
|
|
15
|
+
}
|
|
16
|
+
);
|
|
11
17
|
}
|
|
12
18
|
return name;
|
|
13
19
|
}
|
|
@@ -20,4 +26,4 @@ export {
|
|
|
20
26
|
resolvePackageName,
|
|
21
27
|
getClient
|
|
22
28
|
};
|
|
23
|
-
//# sourceMappingURL=chunk-
|
|
29
|
+
//# sourceMappingURL=chunk-NQH4G7BI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resolve.ts"],"sourcesContent":["// Named exports only. No default export.\n\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\n\nexport function resolvePackageName(\n packageArg: string | undefined,\n config: { app?: string },\n): string {\n const name = packageArg || config.app || process.env[\"GPC_APP\"];\n if (!name) {\n throw Object.assign(\n new Error(\"No package name\"),\n {\n code: \"MISSING_PACKAGE_NAME\",\n exitCode: 2,\n suggestion: \"Use --app <package> or gpc config set app <package>\",\n },\n );\n }\n return name;\n}\n\nexport async function getClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth });\n}\n\nexport async function getReportingClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createReportingClient({ auth });\n}\n"],"mappings":";;;AAGA,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,6BAA6B;AAEhD,SAAS,mBACd,YACA,QACQ;AACR,QAAM,OAAO,cAAc,OAAO,OAAO,QAAQ,IAAI,SAAS;AAC9D,MAAI,CAAC,MAAM;AACT,UAAM,OAAO;AAAA,MACX,IAAI,MAAM,iBAAiB;AAAA,MAC3B;AAAA,QACE,MAAM;AAAA,QACN,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,UAAU,QAAmB;AACjD,QAAM,OAAO,MAAM,YAAY,EAAE,oBAAoB,OAAO,MAAM,eAAe,CAAC;AAClF,SAAO,gBAAgB,EAAE,KAAK,CAAC;AACjC;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/json.ts
|
|
4
|
+
async function readJsonFile(filePath) {
|
|
5
|
+
const { readFile } = await import("fs/promises");
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(await readFile(filePath, "utf-8"));
|
|
8
|
+
} catch (err) {
|
|
9
|
+
throw Object.assign(
|
|
10
|
+
new Error(`Could not read JSON from ${filePath}: ${err instanceof Error ? err.message : String(err)}`),
|
|
11
|
+
{
|
|
12
|
+
code: "INVALID_JSON_FILE",
|
|
13
|
+
exitCode: 2,
|
|
14
|
+
suggestion: `Check that ${filePath} exists and contains valid JSON`
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
readJsonFile
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=chunk-SLNJEAMK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/json.ts"],"sourcesContent":["export async function readJsonFile(filePath: string): Promise<unknown> {\n const { readFile } = await import(\"node:fs/promises\");\n try {\n return JSON.parse(await readFile(filePath, \"utf-8\"));\n } catch (err) {\n throw Object.assign(\n new Error(`Could not read JSON from ${filePath}: ${err instanceof Error ? err.message : String(err)}`),\n {\n code: \"INVALID_JSON_FILE\",\n exitCode: 2,\n suggestion: `Check that ${filePath} exists and contains valid JSON`,\n },\n );\n }\n}\n"],"mappings":";;;AAAA,eAAsB,aAAa,UAAoC;AACrE,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,aAAkB;AACpD,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,SAAS,UAAU,OAAO,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,UAAM,OAAO;AAAA,MACX,IAAI,MAAM,4BAA4B,QAAQ,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MACrG;AAAA,QACE,MAAM;AAAA,QACN,UAAU;AAAA,QACV,YAAY,cAAc,QAAQ;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|