@joe-sh/pj 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1188 @@
1
+ 'use strict';
2
+
3
+ var execa = require('execa');
4
+ var fs = require('fs/promises');
5
+ var path = require('path');
6
+ var fs$1 = require('fs');
7
+ var promises = require('stream/promises');
8
+ var stream = require('stream');
9
+ var tar = require('tar');
10
+ var os = require('os');
11
+ var yaml = require('yaml');
12
+
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
32
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
33
+ var tar__namespace = /*#__PURE__*/_interopNamespace(tar);
34
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
35
+ var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
36
+
37
+ // src/cli/executor.ts
38
+
39
+ // src/api/types.ts
40
+ var PjBinaryError = class extends Error {
41
+ constructor(message, cause) {
42
+ super(message, { cause });
43
+ this.cause = cause;
44
+ this.name = "PjBinaryError";
45
+ }
46
+ };
47
+ var PjExecutionError = class extends Error {
48
+ exitCode;
49
+ stderr;
50
+ constructor(message, exitCode, stderr) {
51
+ super(message);
52
+ this.name = "PjExecutionError";
53
+ this.exitCode = exitCode;
54
+ this.stderr = stderr;
55
+ }
56
+ };
57
+ var PjConfigError = class extends Error {
58
+ constructor(message, cause) {
59
+ super(message, { cause });
60
+ this.cause = cause;
61
+ this.name = "PjConfigError";
62
+ }
63
+ };
64
+ var PJ_TARGET_VERSION = "1.4";
65
+ var GITHUB_OWNER = "josephschmitt";
66
+ var GITHUB_REPO = "pj";
67
+ var GITHUB_API_URL = "https://api.github.com";
68
+ var RELEASES_URL = `${GITHUB_API_URL}/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases`;
69
+ var LATEST_RELEASE_URL = `${RELEASES_URL}/latest`;
70
+ var BINARY_NAME = "pj";
71
+ var BINARY_NAME_WIN = "pj.exe";
72
+ function getBinaryName() {
73
+ return process.platform === "win32" ? BINARY_NAME_WIN : BINARY_NAME;
74
+ }
75
+ function getCacheDir() {
76
+ const xdgCache = process.env["XDG_CACHE_HOME"];
77
+ if (xdgCache) {
78
+ return path__namespace.join(xdgCache, "pj-node");
79
+ }
80
+ if (process.platform === "win32") {
81
+ const localAppData = process.env["LOCALAPPDATA"];
82
+ if (localAppData) {
83
+ return path__namespace.join(localAppData, "pj-node", "cache");
84
+ }
85
+ return path__namespace.join(os__namespace.homedir(), "AppData", "Local", "pj-node", "cache");
86
+ }
87
+ return path__namespace.join(os__namespace.homedir(), ".cache", "pj-node");
88
+ }
89
+ function getBinaryCacheDir() {
90
+ return path__namespace.join(getCacheDir(), "bin");
91
+ }
92
+ function getMetadataPath() {
93
+ return path__namespace.join(getCacheDir(), "metadata.json");
94
+ }
95
+ function getPjConfigDir() {
96
+ const xdgConfig = process.env["XDG_CONFIG_HOME"];
97
+ if (xdgConfig) {
98
+ return path__namespace.join(xdgConfig, "pj");
99
+ }
100
+ if (process.platform === "win32") {
101
+ const appData = process.env["APPDATA"];
102
+ if (appData) {
103
+ return path__namespace.join(appData, "pj");
104
+ }
105
+ return path__namespace.join(os__namespace.homedir(), "AppData", "Roaming", "pj");
106
+ }
107
+ return path__namespace.join(os__namespace.homedir(), ".config", "pj");
108
+ }
109
+ function getPjConfigPath() {
110
+ return path__namespace.join(getPjConfigDir(), "config.yaml");
111
+ }
112
+ function getPjCacheDir() {
113
+ const xdgCache = process.env["XDG_CACHE_HOME"];
114
+ if (xdgCache) {
115
+ return path__namespace.join(xdgCache, "pj");
116
+ }
117
+ if (process.platform === "win32") {
118
+ const localAppData = process.env["LOCALAPPDATA"];
119
+ if (localAppData) {
120
+ return path__namespace.join(localAppData, "pj", "cache");
121
+ }
122
+ return path__namespace.join(os__namespace.homedir(), "AppData", "Local", "pj", "cache");
123
+ }
124
+ return path__namespace.join(os__namespace.homedir(), ".cache", "pj");
125
+ }
126
+ var USER_AGENT = "@joe-sh/pj";
127
+ var HTTP_TIMEOUT = 3e4;
128
+ var UPDATE_CHECK_INTERVAL_DAYS = 7;
129
+
130
+ // src/binary/platform.ts
131
+ function detectPlatform() {
132
+ const nodeOs = process.platform;
133
+ const nodeArch = process.arch;
134
+ let os3;
135
+ let pjOs;
136
+ switch (nodeOs) {
137
+ case "darwin":
138
+ os3 = "darwin";
139
+ pjOs = "darwin";
140
+ break;
141
+ case "linux":
142
+ os3 = "linux";
143
+ pjOs = "linux";
144
+ break;
145
+ case "win32":
146
+ os3 = "win32";
147
+ pjOs = "windows";
148
+ break;
149
+ default:
150
+ throw new PjBinaryError(`Unsupported operating system: ${nodeOs}`);
151
+ }
152
+ let arch;
153
+ let pjArch;
154
+ switch (nodeArch) {
155
+ case "x64":
156
+ arch = "x64";
157
+ pjArch = "amd64";
158
+ break;
159
+ case "arm64":
160
+ arch = "arm64";
161
+ pjArch = "arm64";
162
+ break;
163
+ default:
164
+ throw new PjBinaryError(`Unsupported architecture: ${nodeArch}`);
165
+ }
166
+ return { os: os3, arch, pjOs, pjArch };
167
+ }
168
+ function getAssetFilename(version, platform) {
169
+ const p = platform ?? detectPlatform();
170
+ const versionWithoutV = version.startsWith("v") ? version.slice(1) : version;
171
+ return `pj_${versionWithoutV}_${p.pjOs}_${p.pjArch}.tar.gz`;
172
+ }
173
+ function isPlatformSupported() {
174
+ try {
175
+ detectPlatform();
176
+ return true;
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+
182
+ // src/binary/version.ts
183
+ function parseVersion(version) {
184
+ const normalized = version.replace(/^v/, "");
185
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(normalized);
186
+ if (!match || !match[1] || !match[2] || !match[3]) {
187
+ return null;
188
+ }
189
+ return {
190
+ major: parseInt(match[1], 10),
191
+ minor: parseInt(match[2], 10),
192
+ patch: parseInt(match[3], 10),
193
+ raw: normalized
194
+ };
195
+ }
196
+ function parseTargetVersion(target) {
197
+ const match = /^(\d+)\.(\d+)$/.exec(target);
198
+ if (!match || !match[1] || !match[2]) {
199
+ return null;
200
+ }
201
+ return {
202
+ major: parseInt(match[1], 10),
203
+ minor: parseInt(match[2], 10)
204
+ };
205
+ }
206
+ function isVersionCompatible(version, target = PJ_TARGET_VERSION) {
207
+ const parsed = parseVersion(version);
208
+ const targetParsed = parseTargetVersion(target);
209
+ if (!parsed || !targetParsed) {
210
+ return false;
211
+ }
212
+ return parsed.major === targetParsed.major && parsed.minor === targetParsed.minor;
213
+ }
214
+ function compareVersions(a, b) {
215
+ const parsedA = parseVersion(a);
216
+ const parsedB = parseVersion(b);
217
+ if (!parsedA || !parsedB) {
218
+ return 0;
219
+ }
220
+ if (parsedA.major !== parsedB.major) {
221
+ return parsedA.major - parsedB.major;
222
+ }
223
+ if (parsedA.minor !== parsedB.minor) {
224
+ return parsedA.minor - parsedB.minor;
225
+ }
226
+ return parsedA.patch - parsedB.patch;
227
+ }
228
+ function findHighestCompatibleVersion(versions, target = PJ_TARGET_VERSION) {
229
+ const compatible = versions.filter((v) => isVersionCompatible(v, target));
230
+ if (compatible.length === 0) {
231
+ return null;
232
+ }
233
+ compatible.sort((a, b) => compareVersions(b, a));
234
+ return compatible[0] ?? null;
235
+ }
236
+
237
+ // src/binary/manager.ts
238
+ var BinaryManager = class {
239
+ cachedBinaryPath = null;
240
+ /**
241
+ * Get the path to the pj binary, downloading if necessary
242
+ */
243
+ async getBinaryPath(options) {
244
+ const envPath = process.env["PJ_BINARY_PATH"];
245
+ if (envPath) {
246
+ if (await this.isValidBinary(envPath)) {
247
+ return envPath;
248
+ }
249
+ throw new PjBinaryError(
250
+ `PJ_BINARY_PATH is set but binary is not valid: ${envPath}`
251
+ );
252
+ }
253
+ const globalPath = await this.findGlobalBinary();
254
+ if (globalPath) {
255
+ return globalPath;
256
+ }
257
+ const cachedPath = await this.getCachedBinaryPath();
258
+ if (cachedPath && !options?.force) {
259
+ const needsUpdate = await this.shouldCheckForUpdate();
260
+ if (needsUpdate && options?.version === void 0) {
261
+ try {
262
+ await this.updateBinary(options);
263
+ } catch {
264
+ }
265
+ }
266
+ return cachedPath;
267
+ }
268
+ return await this.downloadBinary(options);
269
+ }
270
+ /**
271
+ * Get the current binary status
272
+ */
273
+ async getStatus() {
274
+ const envPath = process.env["PJ_BINARY_PATH"];
275
+ if (envPath && await this.isValidBinary(envPath)) {
276
+ const version = await this.getVersion(envPath);
277
+ return {
278
+ available: true,
279
+ path: envPath,
280
+ version,
281
+ source: "env"
282
+ };
283
+ }
284
+ const globalPath = await this.findGlobalBinary();
285
+ if (globalPath) {
286
+ const version = await this.getVersion(globalPath);
287
+ return {
288
+ available: true,
289
+ path: globalPath,
290
+ version,
291
+ source: "global"
292
+ };
293
+ }
294
+ const cachedPath = await this.getCachedBinaryPath();
295
+ if (cachedPath) {
296
+ const version = await this.getVersion(cachedPath);
297
+ return {
298
+ available: true,
299
+ path: cachedPath,
300
+ version,
301
+ source: "cache"
302
+ };
303
+ }
304
+ return {
305
+ available: false,
306
+ path: null,
307
+ version: null,
308
+ source: null
309
+ };
310
+ }
311
+ /**
312
+ * Download and install the pj binary.
313
+ * If no specific version is requested, downloads the highest compatible version
314
+ * within the target major.minor range.
315
+ */
316
+ async downloadBinary(options) {
317
+ const release = options?.version ? await this.getRelease(options.version) : await this.getCompatibleRelease();
318
+ const platform = detectPlatform();
319
+ const assetName = getAssetFilename(release.version, platform);
320
+ const asset = release.assets.find((a) => a.name === assetName);
321
+ if (!asset) {
322
+ throw new PjBinaryError(
323
+ `No binary available for ${platform.pjOs}/${platform.pjArch}. Expected asset: ${assetName}`
324
+ );
325
+ }
326
+ const cacheDir = getBinaryCacheDir();
327
+ await fs__namespace.mkdir(cacheDir, { recursive: true });
328
+ const tarballPath = path__namespace.join(cacheDir, assetName);
329
+ await this.downloadAsset(asset, tarballPath, options?.onProgress);
330
+ const binaryName = getBinaryName();
331
+ await tar__namespace.extract({
332
+ file: tarballPath,
333
+ cwd: cacheDir,
334
+ filter: (entryPath) => path__namespace.basename(entryPath) === binaryName
335
+ });
336
+ await fs__namespace.unlink(tarballPath);
337
+ const binaryPath = path__namespace.join(cacheDir, binaryName);
338
+ if (process.platform !== "win32") {
339
+ await fs__namespace.chmod(binaryPath, 493);
340
+ }
341
+ if (!await this.isValidBinary(binaryPath)) {
342
+ throw new PjBinaryError("Downloaded binary failed verification");
343
+ }
344
+ await this.saveMetadata({
345
+ version: release.version,
346
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
347
+ lastUpdateCheck: (/* @__PURE__ */ new Date()).toISOString(),
348
+ source: "download"
349
+ });
350
+ this.cachedBinaryPath = binaryPath;
351
+ return binaryPath;
352
+ }
353
+ /**
354
+ * Update the binary to the highest compatible version within the target range.
355
+ */
356
+ async updateBinary(options) {
357
+ const compatible = await this.getCompatibleRelease();
358
+ const metadata = await this.getMetadata();
359
+ if (metadata?.version === compatible.version && !options?.force) {
360
+ await this.saveMetadata({
361
+ ...metadata,
362
+ lastUpdateCheck: (/* @__PURE__ */ new Date()).toISOString()
363
+ });
364
+ const cachedPath = await this.getCachedBinaryPath();
365
+ if (cachedPath) {
366
+ return cachedPath;
367
+ }
368
+ }
369
+ return await this.downloadBinary({
370
+ ...options,
371
+ version: compatible.version
372
+ });
373
+ }
374
+ /**
375
+ * Get the version of a pj binary
376
+ */
377
+ async getVersion(binaryPath) {
378
+ try {
379
+ const result = await execa.execa(binaryPath, ["--version"], { timeout: 5e3 });
380
+ const match = /(?:pj\s+)?(?:version\s+)?v?(\d+\.\d+\.\d+)/i.exec(
381
+ result.stdout
382
+ );
383
+ return match?.[1] ?? null;
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+ /**
389
+ * Check if a binary path is a valid pj binary
390
+ */
391
+ async isValidBinary(binaryPath) {
392
+ try {
393
+ await fs__namespace.access(binaryPath, fs__namespace.constants.X_OK);
394
+ const version = await this.getVersion(binaryPath);
395
+ return version !== null;
396
+ } catch {
397
+ return false;
398
+ }
399
+ }
400
+ /**
401
+ * Find the globally installed pj binary.
402
+ * Only returns the binary if it's version-compatible with our target.
403
+ */
404
+ async findGlobalBinary() {
405
+ let binaryPath = null;
406
+ try {
407
+ const result = await execa.execa("which", ["pj"], { timeout: 5e3 });
408
+ binaryPath = result.stdout.trim();
409
+ } catch {
410
+ }
411
+ if (!binaryPath && process.platform === "win32") {
412
+ try {
413
+ const result = await execa.execa("where", ["pj"], { timeout: 5e3 });
414
+ binaryPath = result.stdout.trim().split("\n")[0] ?? null;
415
+ } catch {
416
+ }
417
+ }
418
+ if (!binaryPath) {
419
+ return null;
420
+ }
421
+ if (!await this.isValidBinary(binaryPath)) {
422
+ return null;
423
+ }
424
+ const version = await this.getVersion(binaryPath);
425
+ if (!version || !isVersionCompatible(version, PJ_TARGET_VERSION)) {
426
+ return null;
427
+ }
428
+ return binaryPath;
429
+ }
430
+ /**
431
+ * Get the path to the cached binary if it exists and is version-compatible.
432
+ */
433
+ async getCachedBinaryPath() {
434
+ if (this.cachedBinaryPath) {
435
+ if (await this.isValidBinary(this.cachedBinaryPath)) {
436
+ const version = await this.getVersion(this.cachedBinaryPath);
437
+ if (version && isVersionCompatible(version, PJ_TARGET_VERSION)) {
438
+ return this.cachedBinaryPath;
439
+ }
440
+ }
441
+ this.cachedBinaryPath = null;
442
+ }
443
+ const binaryPath = path__namespace.join(getBinaryCacheDir(), getBinaryName());
444
+ if (await this.isValidBinary(binaryPath)) {
445
+ const version = await this.getVersion(binaryPath);
446
+ if (version && isVersionCompatible(version, PJ_TARGET_VERSION)) {
447
+ this.cachedBinaryPath = binaryPath;
448
+ return binaryPath;
449
+ }
450
+ }
451
+ return null;
452
+ }
453
+ /**
454
+ * Check if we should check for updates
455
+ */
456
+ async shouldCheckForUpdate() {
457
+ const metadata = await this.getMetadata();
458
+ if (!metadata) {
459
+ return true;
460
+ }
461
+ const lastCheck = new Date(metadata.lastUpdateCheck);
462
+ const now = /* @__PURE__ */ new Date();
463
+ const daysSinceCheck = (now.getTime() - lastCheck.getTime()) / (1e3 * 60 * 60 * 24);
464
+ return daysSinceCheck >= UPDATE_CHECK_INTERVAL_DAYS;
465
+ }
466
+ /**
467
+ * Get cached metadata
468
+ */
469
+ async getMetadata() {
470
+ try {
471
+ const content = await fs__namespace.readFile(getMetadataPath(), "utf-8");
472
+ return JSON.parse(content);
473
+ } catch {
474
+ return null;
475
+ }
476
+ }
477
+ /**
478
+ * Save metadata to cache
479
+ */
480
+ async saveMetadata(metadata) {
481
+ const metadataPath = getMetadataPath();
482
+ await fs__namespace.mkdir(path__namespace.dirname(metadataPath), { recursive: true });
483
+ await fs__namespace.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
484
+ }
485
+ /**
486
+ * Get all releases from GitHub (up to 100 most recent)
487
+ */
488
+ async getAllReleases() {
489
+ const response = await fetch(`${RELEASES_URL}?per_page=100`, {
490
+ headers: {
491
+ Accept: "application/vnd.github.v3+json",
492
+ "User-Agent": USER_AGENT
493
+ },
494
+ signal: AbortSignal.timeout(HTTP_TIMEOUT)
495
+ });
496
+ if (!response.ok) {
497
+ throw new PjBinaryError(
498
+ `Failed to fetch releases: ${String(response.status)} ${response.statusText}`
499
+ );
500
+ }
501
+ const data = await response.json();
502
+ return data.filter((release) => !release.prerelease).map((release) => this.parseRelease(release));
503
+ }
504
+ /**
505
+ * Get the highest compatible release within the target major.minor range.
506
+ */
507
+ async getCompatibleRelease() {
508
+ const releases = await this.getAllReleases();
509
+ const versions = releases.map((r) => r.version);
510
+ const compatibleVersion = findHighestCompatibleVersion(
511
+ versions,
512
+ PJ_TARGET_VERSION
513
+ );
514
+ if (!compatibleVersion) {
515
+ throw new PjBinaryError(
516
+ `No compatible pj release found for version range ${PJ_TARGET_VERSION}.x. Available versions: ${versions.join(", ")}`
517
+ );
518
+ }
519
+ const release = releases.find((r) => r.version === compatibleVersion);
520
+ if (!release) {
521
+ throw new PjBinaryError(
522
+ `Failed to find release for version ${compatibleVersion}`
523
+ );
524
+ }
525
+ return release;
526
+ }
527
+ /**
528
+ * Get the latest release from GitHub
529
+ */
530
+ async getLatestRelease() {
531
+ const response = await fetch(LATEST_RELEASE_URL, {
532
+ headers: {
533
+ Accept: "application/vnd.github.v3+json",
534
+ "User-Agent": USER_AGENT
535
+ },
536
+ signal: AbortSignal.timeout(HTTP_TIMEOUT)
537
+ });
538
+ if (!response.ok) {
539
+ throw new PjBinaryError(
540
+ `Failed to fetch latest release: ${String(response.status)} ${response.statusText}`
541
+ );
542
+ }
543
+ const data = await response.json();
544
+ return this.parseRelease(data);
545
+ }
546
+ /**
547
+ * Get a specific release from GitHub
548
+ */
549
+ async getRelease(version) {
550
+ const tag = version.startsWith("v") ? version : `v${version}`;
551
+ const url = `${LATEST_RELEASE_URL.replace("/latest", "")}/${tag}`;
552
+ const response = await fetch(url, {
553
+ headers: {
554
+ Accept: "application/vnd.github.v3+json",
555
+ "User-Agent": USER_AGENT
556
+ },
557
+ signal: AbortSignal.timeout(HTTP_TIMEOUT)
558
+ });
559
+ if (!response.ok) {
560
+ throw new PjBinaryError(
561
+ `Failed to fetch release ${version}: ${String(response.status)} ${response.statusText}`
562
+ );
563
+ }
564
+ const data = await response.json();
565
+ return this.parseRelease(data);
566
+ }
567
+ /**
568
+ * Parse GitHub release response
569
+ */
570
+ parseRelease(data) {
571
+ return {
572
+ tagName: data.tag_name,
573
+ version: data.tag_name.replace(/^v/, ""),
574
+ name: data.name,
575
+ prerelease: data.prerelease,
576
+ assets: data.assets.map((asset) => ({
577
+ name: asset.name,
578
+ downloadUrl: asset.browser_download_url,
579
+ size: asset.size,
580
+ contentType: asset.content_type
581
+ }))
582
+ };
583
+ }
584
+ /**
585
+ * Download an asset to a file
586
+ */
587
+ async downloadAsset(asset, destPath, onProgress) {
588
+ const response = await fetch(asset.downloadUrl, {
589
+ headers: {
590
+ "User-Agent": USER_AGENT
591
+ },
592
+ signal: AbortSignal.timeout(HTTP_TIMEOUT * 10)
593
+ // Longer timeout for downloads
594
+ });
595
+ if (!response.ok) {
596
+ throw new PjBinaryError(
597
+ `Failed to download asset: ${String(response.status)} ${response.statusText}`
598
+ );
599
+ }
600
+ if (!response.body) {
601
+ throw new PjBinaryError("Response body is empty");
602
+ }
603
+ const total = asset.size;
604
+ let downloaded = 0;
605
+ const progressStream = new TransformStream({
606
+ transform(chunk, controller) {
607
+ downloaded += chunk.length;
608
+ onProgress?.({
609
+ downloaded,
610
+ total,
611
+ percent: Math.round(downloaded / total * 100)
612
+ });
613
+ controller.enqueue(chunk);
614
+ }
615
+ });
616
+ const fileStream = fs$1.createWriteStream(destPath);
617
+ await promises.pipeline(
618
+ stream.Readable.fromWeb(response.body.pipeThrough(progressStream)),
619
+ fileStream
620
+ );
621
+ }
622
+ /**
623
+ * Clear the binary cache
624
+ */
625
+ async clearCache() {
626
+ const cacheDir = getBinaryCacheDir();
627
+ try {
628
+ await fs__namespace.rm(cacheDir, { recursive: true, force: true });
629
+ } catch {
630
+ }
631
+ const metadataPath = getMetadataPath();
632
+ try {
633
+ await fs__namespace.unlink(metadataPath);
634
+ } catch {
635
+ }
636
+ this.cachedBinaryPath = null;
637
+ }
638
+ };
639
+ var binaryManager = null;
640
+ function getBinaryManager() {
641
+ binaryManager ??= new BinaryManager();
642
+ return binaryManager;
643
+ }
644
+
645
+ // src/cli/executor.ts
646
+ function buildArgs(options) {
647
+ const args = [];
648
+ if (options?.paths) {
649
+ for (const p of options.paths) {
650
+ args.push("--path", p);
651
+ }
652
+ }
653
+ if (options?.markers) {
654
+ for (const m of options.markers) {
655
+ args.push("--marker", m);
656
+ }
657
+ }
658
+ if (options?.excludes) {
659
+ for (const e of options.excludes) {
660
+ args.push("--exclude", e);
661
+ }
662
+ }
663
+ if (options?.maxDepth !== void 0) {
664
+ args.push("--max-depth", String(options.maxDepth));
665
+ }
666
+ if (options?.noIgnore) {
667
+ args.push("--no-ignore");
668
+ }
669
+ if (options?.nested === false) {
670
+ args.push("--no-nested");
671
+ }
672
+ if (options?.noCache) {
673
+ args.push("--no-cache");
674
+ }
675
+ if (options?.icons) {
676
+ args.push("--icons");
677
+ }
678
+ if (options?.configPath) {
679
+ args.push("--config", options.configPath);
680
+ }
681
+ if (options?.verbose) {
682
+ args.push("--verbose");
683
+ }
684
+ args.push("--json");
685
+ return args;
686
+ }
687
+ function parseJsonOutput(output) {
688
+ if (!output.trim()) {
689
+ return [];
690
+ }
691
+ try {
692
+ const parsed = JSON.parse(output);
693
+ return parsed.map((p) => ({
694
+ path: p.path,
695
+ name: p.name,
696
+ marker: p.marker,
697
+ icon: p.icon,
698
+ priority: p.priority
699
+ }));
700
+ } catch (error) {
701
+ throw new PjExecutionError(
702
+ `Failed to parse pj output: ${error instanceof Error ? error.message : String(error)}`
703
+ );
704
+ }
705
+ }
706
+ async function executePj(args, execaOptions) {
707
+ const binaryManager2 = getBinaryManager();
708
+ const binaryPath = await binaryManager2.getBinaryPath();
709
+ try {
710
+ const result = await execa.execa(binaryPath, args, {
711
+ timeout: 6e4,
712
+ // 1 minute timeout
713
+ ...execaOptions
714
+ });
715
+ return {
716
+ stdout: String(result.stdout ?? ""),
717
+ stderr: String(result.stderr ?? ""),
718
+ exitCode: result.exitCode ?? 0
719
+ };
720
+ } catch (error) {
721
+ if (error instanceof Error && "exitCode" in error) {
722
+ const execaError = error;
723
+ throw new PjExecutionError(
724
+ `pj command failed: ${execaError.message}`,
725
+ execaError.exitCode,
726
+ execaError.stderr
727
+ );
728
+ }
729
+ throw error;
730
+ }
731
+ }
732
+ async function executePjWithStdin(args, stdin, execaOptions) {
733
+ const binaryManager2 = getBinaryManager();
734
+ const binaryPath = await binaryManager2.getBinaryPath();
735
+ try {
736
+ const result = await execa.execa(binaryPath, args, {
737
+ input: stdin,
738
+ timeout: 6e4,
739
+ ...execaOptions
740
+ });
741
+ return {
742
+ stdout: String(result.stdout ?? ""),
743
+ stderr: String(result.stderr ?? ""),
744
+ exitCode: result.exitCode ?? 0
745
+ };
746
+ } catch (error) {
747
+ if (error instanceof Error && "exitCode" in error) {
748
+ const execaError = error;
749
+ throw new PjExecutionError(
750
+ `pj command failed: ${execaError.message}`,
751
+ execaError.exitCode,
752
+ execaError.stderr
753
+ );
754
+ }
755
+ throw error;
756
+ }
757
+ }
758
+
759
+ // src/api/discover.ts
760
+ async function discover(options) {
761
+ const args = buildArgs(options);
762
+ const result = await executePj(args);
763
+ return parseJsonOutput(result.stdout);
764
+ }
765
+ async function discoverFromPaths(paths, options) {
766
+ const optsWithoutPaths = {};
767
+ if (options?.markers !== void 0) optsWithoutPaths.markers = options.markers;
768
+ if (options?.excludes !== void 0) optsWithoutPaths.excludes = options.excludes;
769
+ if (options?.maxDepth !== void 0) optsWithoutPaths.maxDepth = options.maxDepth;
770
+ if (options?.noIgnore !== void 0) optsWithoutPaths.noIgnore = options.noIgnore;
771
+ if (options?.nested !== void 0) optsWithoutPaths.nested = options.nested;
772
+ if (options?.noCache !== void 0) optsWithoutPaths.noCache = options.noCache;
773
+ if (options?.icons !== void 0) optsWithoutPaths.icons = options.icons;
774
+ if (options?.configPath !== void 0) optsWithoutPaths.configPath = options.configPath;
775
+ if (options?.verbose !== void 0) optsWithoutPaths.verbose = options.verbose;
776
+ const args = buildArgs(optsWithoutPaths);
777
+ const stdin = paths.join("\n");
778
+ const result = await executePjWithStdin(args, stdin);
779
+ return parseJsonOutput(result.stdout);
780
+ }
781
+ async function findProject(name, options) {
782
+ const projects = await discover(options);
783
+ return projects.find(
784
+ (p) => p.name === name || p.name.toLowerCase() === name.toLowerCase()
785
+ );
786
+ }
787
+ async function findProjects(pattern, options) {
788
+ const projects = await discover(options);
789
+ const regex = typeof pattern === "string" ? new RegExp(pattern, "i") : pattern;
790
+ return projects.filter((p) => regex.test(p.name) || regex.test(p.path));
791
+ }
792
+ async function discoverByMarker(options) {
793
+ const projects = await discover(options);
794
+ const grouped = /* @__PURE__ */ new Map();
795
+ for (const project of projects) {
796
+ const existing = grouped.get(project.marker);
797
+ if (existing) {
798
+ existing.push(project);
799
+ } else {
800
+ grouped.set(project.marker, [project]);
801
+ }
802
+ }
803
+ return grouped;
804
+ }
805
+ async function countByMarker(options) {
806
+ const grouped = await discoverByMarker(options);
807
+ const counts = /* @__PURE__ */ new Map();
808
+ for (const [marker, projects] of grouped) {
809
+ counts.set(marker, projects.length);
810
+ }
811
+ return counts;
812
+ }
813
+ var DEFAULT_CONFIG = {
814
+ paths: [
815
+ path__namespace.join(os__namespace.homedir(), "projects"),
816
+ path__namespace.join(os__namespace.homedir(), "code"),
817
+ path__namespace.join(os__namespace.homedir(), "development")
818
+ ],
819
+ markers: [
820
+ ".git",
821
+ "go.mod",
822
+ "package.json",
823
+ "Cargo.toml",
824
+ "pyproject.toml",
825
+ ".vscode",
826
+ ".idea",
827
+ "Makefile"
828
+ ],
829
+ exclude: ["node_modules", "vendor", ".cache", "target", "dist", "build"],
830
+ maxDepth: 3,
831
+ cacheTTL: 300,
832
+ noIgnore: false,
833
+ noNested: false,
834
+ icons: {
835
+ ".git": "\uF1D3 ",
836
+ //
837
+ "package.json": "\uE718 ",
838
+ //
839
+ "go.mod": "\uE626 ",
840
+ //
841
+ "Cargo.toml": "\uE7A8 ",
842
+ //
843
+ "pyproject.toml": "\uE73C ",
844
+ //
845
+ ".vscode": "\uE70C ",
846
+ //
847
+ ".idea": "\uE7B5 ",
848
+ //
849
+ Makefile: "\uE779 "
850
+ //
851
+ }
852
+ };
853
+ async function loadConfig(configPath) {
854
+ const filePath = configPath ?? getPjConfigPath();
855
+ try {
856
+ const content = await fs__namespace.readFile(filePath, "utf-8");
857
+ const parsed = yaml__namespace.parse(content);
858
+ return mergeConfig(parsed);
859
+ } catch (error) {
860
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
861
+ return { ...DEFAULT_CONFIG };
862
+ }
863
+ throw new PjConfigError(
864
+ `Failed to load config from ${filePath}`,
865
+ error instanceof Error ? error : void 0
866
+ );
867
+ }
868
+ }
869
+ async function saveConfig(config, configPath) {
870
+ const filePath = configPath ?? getPjConfigPath();
871
+ const rawConfig = {};
872
+ if (config.paths !== void 0) rawConfig.paths = config.paths;
873
+ if (config.markers !== void 0) rawConfig.markers = config.markers;
874
+ if (config.exclude !== void 0) rawConfig.exclude = config.exclude;
875
+ if (config.maxDepth !== void 0) rawConfig.max_depth = config.maxDepth;
876
+ if (config.cacheTTL !== void 0) rawConfig.cache_ttl = config.cacheTTL;
877
+ if (config.noIgnore !== void 0) rawConfig.no_ignore = config.noIgnore;
878
+ if (config.noNested !== void 0) rawConfig.no_nested = config.noNested;
879
+ if (config.icons !== void 0) rawConfig.icons = config.icons;
880
+ const content = yaml__namespace.stringify(rawConfig, { indent: 2 });
881
+ await fs__namespace.mkdir(path__namespace.dirname(filePath), { recursive: true });
882
+ await fs__namespace.writeFile(filePath, content, "utf-8");
883
+ }
884
+ async function configExists(configPath) {
885
+ const filePath = configPath ?? getPjConfigPath();
886
+ try {
887
+ await fs__namespace.access(filePath);
888
+ return true;
889
+ } catch {
890
+ return false;
891
+ }
892
+ }
893
+ function getConfigPath() {
894
+ return getPjConfigPath();
895
+ }
896
+ function mergeConfig(raw) {
897
+ return {
898
+ paths: raw.paths ?? DEFAULT_CONFIG.paths,
899
+ markers: raw.markers ?? DEFAULT_CONFIG.markers,
900
+ exclude: raw.exclude ?? DEFAULT_CONFIG.exclude,
901
+ maxDepth: raw.max_depth ?? DEFAULT_CONFIG.maxDepth,
902
+ cacheTTL: raw.cache_ttl ?? DEFAULT_CONFIG.cacheTTL,
903
+ noIgnore: raw.no_ignore ?? DEFAULT_CONFIG.noIgnore,
904
+ noNested: raw.no_nested ?? DEFAULT_CONFIG.noNested,
905
+ icons: raw.icons ?? DEFAULT_CONFIG.icons
906
+ };
907
+ }
908
+ function expandPath(p) {
909
+ if (p.startsWith("~/")) {
910
+ return path__namespace.join(os__namespace.homedir(), p.slice(2));
911
+ }
912
+ if (p === "~") {
913
+ return os__namespace.homedir();
914
+ }
915
+ return p;
916
+ }
917
+ function expandConfigPaths(config) {
918
+ return {
919
+ ...config,
920
+ paths: config.paths.map(expandPath)
921
+ };
922
+ }
923
+ async function clearCache() {
924
+ await executePj(["--clear-cache"]);
925
+ }
926
+ async function getCacheInfo() {
927
+ const cacheDir = getPjCacheDir();
928
+ try {
929
+ const stats = await fs__namespace.stat(cacheDir);
930
+ if (!stats.isDirectory()) {
931
+ return {
932
+ exists: false,
933
+ path: cacheDir,
934
+ fileCount: 0,
935
+ totalSize: 0
936
+ };
937
+ }
938
+ const files = await fs__namespace.readdir(cacheDir);
939
+ let totalSize = 0;
940
+ let fileCount = 0;
941
+ for (const file of files) {
942
+ if (file.endsWith(".json")) {
943
+ const filePath = path__namespace.join(cacheDir, file);
944
+ const fileStats = await fs__namespace.stat(filePath);
945
+ totalSize += fileStats.size;
946
+ fileCount++;
947
+ }
948
+ }
949
+ return {
950
+ exists: true,
951
+ path: cacheDir,
952
+ fileCount,
953
+ totalSize
954
+ };
955
+ } catch (error) {
956
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
957
+ return {
958
+ exists: false,
959
+ path: cacheDir,
960
+ fileCount: 0,
961
+ totalSize: 0
962
+ };
963
+ }
964
+ throw error;
965
+ }
966
+ }
967
+ function getCachePath() {
968
+ return getPjCacheDir();
969
+ }
970
+
971
+ // src/api/pj.ts
972
+ var Pj = class {
973
+ config;
974
+ /**
975
+ * Create a new Pj instance
976
+ *
977
+ * @param config - Optional configuration overrides
978
+ */
979
+ constructor(config) {
980
+ this.config = { ...DEFAULT_CONFIG, ...config };
981
+ }
982
+ /**
983
+ * Discover all projects
984
+ *
985
+ * @param options - Discovery options
986
+ * @returns Array of discovered projects
987
+ */
988
+ async discover(options) {
989
+ return discover(this.mergeOptions(options));
990
+ }
991
+ /**
992
+ * Discover projects from specific paths
993
+ *
994
+ * Bypasses configured paths and searches only the provided paths.
995
+ * Useful for integration with other tools.
996
+ *
997
+ * @param paths - Paths to search
998
+ * @param options - Additional discovery options
999
+ */
1000
+ async discoverFromPaths(paths, options) {
1001
+ return discoverFromPaths(paths, this.mergeOptions(options));
1002
+ }
1003
+ /**
1004
+ * Find a project by name
1005
+ *
1006
+ * @param name - Project name to find
1007
+ * @param options - Discovery options
1008
+ */
1009
+ async findProject(name, options) {
1010
+ return findProject(name, this.mergeOptions(options));
1011
+ }
1012
+ /**
1013
+ * Find projects matching a pattern
1014
+ *
1015
+ * @param pattern - String or regex pattern to match
1016
+ * @param options - Discovery options
1017
+ */
1018
+ async findProjects(pattern, options) {
1019
+ return findProjects(pattern, this.mergeOptions(options));
1020
+ }
1021
+ /**
1022
+ * Get projects grouped by marker type
1023
+ *
1024
+ * @param options - Discovery options
1025
+ */
1026
+ async discoverByMarker(options) {
1027
+ return discoverByMarker(this.mergeOptions(options));
1028
+ }
1029
+ /**
1030
+ * Count projects by marker type
1031
+ *
1032
+ * @param options - Discovery options
1033
+ */
1034
+ async countByMarker(options) {
1035
+ return countByMarker(this.mergeOptions(options));
1036
+ }
1037
+ /**
1038
+ * Clear the pj project cache
1039
+ */
1040
+ async clearCache() {
1041
+ return clearCache();
1042
+ }
1043
+ /**
1044
+ * Get information about the pj cache
1045
+ */
1046
+ async getCacheInfo() {
1047
+ return getCacheInfo();
1048
+ }
1049
+ /**
1050
+ * Load configuration from file
1051
+ *
1052
+ * @param configPath - Optional path to config file
1053
+ */
1054
+ async loadConfig(configPath) {
1055
+ const loaded = await loadConfig(configPath);
1056
+ this.config = loaded;
1057
+ return loaded;
1058
+ }
1059
+ /**
1060
+ * Save configuration to file
1061
+ *
1062
+ * @param config - Configuration to save (uses current config if not provided)
1063
+ * @param configPath - Optional path to config file
1064
+ */
1065
+ async saveConfig(config, configPath) {
1066
+ const toSave = config ? { ...this.config, ...config } : this.config;
1067
+ await saveConfig(toSave, configPath);
1068
+ }
1069
+ /**
1070
+ * Get current configuration
1071
+ */
1072
+ getConfig() {
1073
+ return { ...this.config };
1074
+ }
1075
+ /**
1076
+ * Update configuration
1077
+ *
1078
+ * @param config - Partial configuration to merge
1079
+ */
1080
+ setConfig(config) {
1081
+ this.config = { ...this.config, ...config };
1082
+ }
1083
+ /**
1084
+ * Ensure the pj binary is available
1085
+ *
1086
+ * Downloads the binary if not already installed.
1087
+ *
1088
+ * @param options - Binary options
1089
+ * @returns Path to the binary
1090
+ */
1091
+ async ensureBinary(options) {
1092
+ const manager = getBinaryManager();
1093
+ return manager.getBinaryPath(options);
1094
+ }
1095
+ /**
1096
+ * Get the status of the pj binary
1097
+ */
1098
+ async getBinaryStatus() {
1099
+ const manager = getBinaryManager();
1100
+ return manager.getStatus();
1101
+ }
1102
+ /**
1103
+ * Update the pj binary to the latest version
1104
+ *
1105
+ * @param options - Binary options
1106
+ * @returns Path to the updated binary
1107
+ */
1108
+ async updateBinary(options) {
1109
+ const manager = getBinaryManager();
1110
+ return manager.updateBinary(options);
1111
+ }
1112
+ /**
1113
+ * Get the version of the pj binary
1114
+ */
1115
+ async getBinaryVersion() {
1116
+ const manager = getBinaryManager();
1117
+ const status = await manager.getStatus();
1118
+ return status.version;
1119
+ }
1120
+ /**
1121
+ * Merge instance config with provided options
1122
+ */
1123
+ mergeOptions(options) {
1124
+ const merged = {
1125
+ paths: options?.paths ?? this.config.paths,
1126
+ markers: options?.markers ?? this.config.markers,
1127
+ excludes: options?.excludes ?? this.config.exclude,
1128
+ maxDepth: options?.maxDepth ?? this.config.maxDepth,
1129
+ noIgnore: options?.noIgnore ?? this.config.noIgnore,
1130
+ nested: options?.nested ?? !this.config.noNested
1131
+ };
1132
+ if (options?.icons !== void 0) {
1133
+ merged.icons = options.icons;
1134
+ }
1135
+ if (options?.noCache !== void 0) {
1136
+ merged.noCache = options.noCache;
1137
+ }
1138
+ if (options?.configPath !== void 0) {
1139
+ merged.configPath = options.configPath;
1140
+ }
1141
+ if (options?.verbose !== void 0) {
1142
+ merged.verbose = options.verbose;
1143
+ }
1144
+ return merged;
1145
+ }
1146
+ };
1147
+
1148
+ exports.BinaryManager = BinaryManager;
1149
+ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
1150
+ exports.GITHUB_OWNER = GITHUB_OWNER;
1151
+ exports.GITHUB_REPO = GITHUB_REPO;
1152
+ exports.PJ_TARGET_VERSION = PJ_TARGET_VERSION;
1153
+ exports.Pj = Pj;
1154
+ exports.PjBinaryError = PjBinaryError;
1155
+ exports.PjConfigError = PjConfigError;
1156
+ exports.PjExecutionError = PjExecutionError;
1157
+ exports.buildArgs = buildArgs;
1158
+ exports.clearCache = clearCache;
1159
+ exports.compareVersions = compareVersions;
1160
+ exports.configExists = configExists;
1161
+ exports.countByMarker = countByMarker;
1162
+ exports.detectPlatform = detectPlatform;
1163
+ exports.discover = discover;
1164
+ exports.discoverByMarker = discoverByMarker;
1165
+ exports.discoverFromPaths = discoverFromPaths;
1166
+ exports.executePj = executePj;
1167
+ exports.expandConfigPaths = expandConfigPaths;
1168
+ exports.expandPath = expandPath;
1169
+ exports.findHighestCompatibleVersion = findHighestCompatibleVersion;
1170
+ exports.findProject = findProject;
1171
+ exports.findProjects = findProjects;
1172
+ exports.getAssetFilename = getAssetFilename;
1173
+ exports.getBinaryCacheDir = getBinaryCacheDir;
1174
+ exports.getBinaryManager = getBinaryManager;
1175
+ exports.getBinaryName = getBinaryName;
1176
+ exports.getCacheDir = getCacheDir;
1177
+ exports.getCacheInfo = getCacheInfo;
1178
+ exports.getCachePath = getCachePath;
1179
+ exports.getConfigPath = getConfigPath;
1180
+ exports.isPlatformSupported = isPlatformSupported;
1181
+ exports.isVersionCompatible = isVersionCompatible;
1182
+ exports.loadConfig = loadConfig;
1183
+ exports.parseJsonOutput = parseJsonOutput;
1184
+ exports.parseTargetVersion = parseTargetVersion;
1185
+ exports.parseVersion = parseVersion;
1186
+ exports.saveConfig = saveConfig;
1187
+ //# sourceMappingURL=index.cjs.map
1188
+ //# sourceMappingURL=index.cjs.map