@offworld/sdk 0.2.2 → 0.3.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/README.md +35 -20
- package/dist/ai/index.d.mts +134 -0
- package/dist/ai/index.d.mts.map +1 -0
- package/dist/ai/index.mjs +924 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/clone-DyLvmbJZ.mjs +364 -0
- package/dist/clone-DyLvmbJZ.mjs.map +1 -0
- package/dist/config-DW8J4gl5.mjs +174 -0
- package/dist/config-DW8J4gl5.mjs.map +1 -0
- package/dist/convex/_generated/api.d.ts +67 -0
- package/dist/convex/_generated/api.js +23 -0
- package/dist/convex/_generated/dataModel.d.ts +60 -0
- package/dist/convex/_generated/server.d.ts +143 -0
- package/dist/convex/_generated/server.js +93 -0
- package/dist/index.d.mts +2 -953
- package/dist/index.mjs +4 -3909
- package/dist/internal.d.mts +69 -0
- package/dist/internal.d.mts.map +1 -0
- package/dist/internal.mjs +326 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/public-DbZeh2Mr.mjs +1823 -0
- package/dist/public-DbZeh2Mr.mjs.map +1 -0
- package/dist/public-MYVLaKUi.d.mts +655 -0
- package/dist/public-MYVLaKUi.d.mts.map +1 -0
- package/dist/sync/index.d.mts +175 -0
- package/dist/sync/index.d.mts.map +1 -0
- package/dist/sync/index.mjs +4 -0
- package/dist/sync-DuLJ5wla.mjs +4 -0
- package/dist/sync-wcy5fJRb.mjs +372 -0
- package/dist/sync-wcy5fJRb.mjs.map +1 -0
- package/package.json +35 -6
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { RepoSource } from "@offworld/types";
|
|
2
|
+
import "convex/browser";
|
|
3
|
+
|
|
4
|
+
//#region src/sync/client.d.ts
|
|
5
|
+
declare class SyncUnavailableError extends Error {
|
|
6
|
+
constructor(message?: string);
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/sync.d.ts
|
|
10
|
+
declare class SyncError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
declare class NetworkError extends SyncError {
|
|
14
|
+
readonly statusCode?: number | undefined;
|
|
15
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
16
|
+
}
|
|
17
|
+
declare class AuthenticationError extends SyncError {
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
20
|
+
declare class RateLimitError extends SyncError {
|
|
21
|
+
constructor(message?: string);
|
|
22
|
+
}
|
|
23
|
+
declare class ConflictError extends SyncError {
|
|
24
|
+
readonly remoteCommitSha?: string | undefined;
|
|
25
|
+
constructor(message?: string, remoteCommitSha?: string | undefined);
|
|
26
|
+
}
|
|
27
|
+
declare class CommitExistsError extends SyncError {
|
|
28
|
+
constructor(message?: string);
|
|
29
|
+
}
|
|
30
|
+
declare class InvalidInputError extends SyncError {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
declare class InvalidReferenceError extends SyncError {
|
|
34
|
+
constructor(message: string);
|
|
35
|
+
}
|
|
36
|
+
declare class RepoNotFoundError extends SyncError {
|
|
37
|
+
constructor(message?: string);
|
|
38
|
+
}
|
|
39
|
+
declare class LowStarsError extends SyncError {
|
|
40
|
+
constructor(message?: string);
|
|
41
|
+
}
|
|
42
|
+
declare class PrivateRepoError extends SyncError {
|
|
43
|
+
constructor(message?: string);
|
|
44
|
+
}
|
|
45
|
+
declare class CommitNotFoundError extends SyncError {
|
|
46
|
+
constructor(message?: string);
|
|
47
|
+
}
|
|
48
|
+
declare class GitHubError extends SyncError {
|
|
49
|
+
constructor(message?: string);
|
|
50
|
+
}
|
|
51
|
+
declare class PushNotAllowedError extends SyncError {
|
|
52
|
+
readonly reason: "local" | "not-github";
|
|
53
|
+
constructor(message: string, reason: "local" | "not-github");
|
|
54
|
+
}
|
|
55
|
+
/** Reference data structure for sync operations */
|
|
56
|
+
interface ReferenceData {
|
|
57
|
+
fullName: string;
|
|
58
|
+
referenceName: string;
|
|
59
|
+
referenceDescription: string;
|
|
60
|
+
referenceContent: string;
|
|
61
|
+
commitSha: string;
|
|
62
|
+
generatedAt: string;
|
|
63
|
+
}
|
|
64
|
+
/** Response from pull query */
|
|
65
|
+
interface PullResponse {
|
|
66
|
+
fullName: string;
|
|
67
|
+
referenceName: string;
|
|
68
|
+
referenceDescription: string;
|
|
69
|
+
referenceContent: string;
|
|
70
|
+
commitSha: string;
|
|
71
|
+
generatedAt: string;
|
|
72
|
+
}
|
|
73
|
+
/** Response from check query */
|
|
74
|
+
interface CheckResponse {
|
|
75
|
+
exists: boolean;
|
|
76
|
+
commitSha?: string;
|
|
77
|
+
generatedAt?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Response from push mutation */
|
|
80
|
+
interface PushResponse {
|
|
81
|
+
success: boolean;
|
|
82
|
+
message?: string;
|
|
83
|
+
}
|
|
84
|
+
/** Staleness check result */
|
|
85
|
+
interface StalenessResult {
|
|
86
|
+
isStale: boolean;
|
|
87
|
+
localCommitSha?: string;
|
|
88
|
+
remoteCommitSha?: string;
|
|
89
|
+
}
|
|
90
|
+
/** Can push result */
|
|
91
|
+
interface CanPushResult {
|
|
92
|
+
allowed: boolean;
|
|
93
|
+
reason?: string;
|
|
94
|
+
stars?: number;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Fetches reference from the remote server
|
|
98
|
+
* @param fullName - Repository full name (owner/repo)
|
|
99
|
+
* @returns Reference data or null if not found
|
|
100
|
+
*/
|
|
101
|
+
declare function pullReference(fullName: string): Promise<PullResponse | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Fetches a specific reference by name from the remote server
|
|
104
|
+
* @param fullName - Repository full name (owner/repo)
|
|
105
|
+
* @param referenceName - Specific reference name to pull
|
|
106
|
+
* @returns Reference data or null if not found
|
|
107
|
+
*/
|
|
108
|
+
declare function pullReferenceByName(fullName: string, referenceName: string): Promise<PullResponse | null>;
|
|
109
|
+
/**
|
|
110
|
+
* Pushes reference to the remote server
|
|
111
|
+
* All validation happens server-side
|
|
112
|
+
* @param reference - Reference data to push
|
|
113
|
+
* @param token - Authentication token
|
|
114
|
+
* @returns Push result
|
|
115
|
+
*/
|
|
116
|
+
declare function pushReference(reference: ReferenceData, token: string): Promise<PushResponse>;
|
|
117
|
+
/**
|
|
118
|
+
* Checks if reference exists on remote server (lightweight check)
|
|
119
|
+
* @param fullName - Repository full name (owner/repo)
|
|
120
|
+
* @returns Check result
|
|
121
|
+
*/
|
|
122
|
+
declare function checkRemote(fullName: string): Promise<CheckResponse>;
|
|
123
|
+
/**
|
|
124
|
+
* Checks if a specific reference exists on the remote server
|
|
125
|
+
* @param fullName - Repository full name (owner/repo)
|
|
126
|
+
* @param referenceName - Specific reference name to check
|
|
127
|
+
* @returns Check result
|
|
128
|
+
*/
|
|
129
|
+
declare function checkRemoteByName(fullName: string, referenceName: string): Promise<CheckResponse>;
|
|
130
|
+
/**
|
|
131
|
+
* Compares local vs remote commit SHA to check staleness
|
|
132
|
+
* @param fullName - Repository full name (owner/repo)
|
|
133
|
+
* @param localCommitSha - Local commit SHA
|
|
134
|
+
* @returns Staleness result
|
|
135
|
+
*/
|
|
136
|
+
declare function checkStaleness(fullName: string, localCommitSha: string): Promise<StalenessResult>;
|
|
137
|
+
/** GitHub repository metadata */
|
|
138
|
+
interface GitHubRepoMetadata {
|
|
139
|
+
stars: number;
|
|
140
|
+
description?: string;
|
|
141
|
+
language?: string;
|
|
142
|
+
defaultBranch: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Fetches GitHub repository metadata
|
|
146
|
+
* @param owner - Repository owner
|
|
147
|
+
* @param repo - Repository name
|
|
148
|
+
* @returns Repository metadata or null on error
|
|
149
|
+
*/
|
|
150
|
+
declare function fetchGitHubMetadata(owner: string, repo: string): Promise<GitHubRepoMetadata | null>;
|
|
151
|
+
/**
|
|
152
|
+
* Fetches GitHub repository stars
|
|
153
|
+
* @param owner - Repository owner
|
|
154
|
+
* @param repo - Repository name
|
|
155
|
+
* @returns Number of stars, or 0 on error
|
|
156
|
+
*/
|
|
157
|
+
declare function fetchRepoStars(owner: string, repo: string): Promise<number>;
|
|
158
|
+
/**
|
|
159
|
+
* Checks if a repository can be pushed to offworld.sh (client-side quick checks)
|
|
160
|
+
* Note: Star count and other validations happen server-side
|
|
161
|
+
*
|
|
162
|
+
* @param source - Repository source
|
|
163
|
+
* @returns Can push result
|
|
164
|
+
*/
|
|
165
|
+
declare function canPushToWeb(source: RepoSource): CanPushResult;
|
|
166
|
+
/**
|
|
167
|
+
* Validates that a source can be pushed and throws appropriate error if not
|
|
168
|
+
* Note: This only does quick client-side checks. Full validation happens server-side.
|
|
169
|
+
* @param source - Repository source
|
|
170
|
+
* @throws PushNotAllowedError if push is not allowed
|
|
171
|
+
*/
|
|
172
|
+
declare function validatePushAllowed(source: RepoSource): void;
|
|
173
|
+
//#endregion
|
|
174
|
+
export { AuthenticationError, CanPushResult, CheckResponse, CommitExistsError, CommitNotFoundError, ConflictError, GitHubError, GitHubRepoMetadata, InvalidInputError, InvalidReferenceError, LowStarsError, NetworkError, PrivateRepoError, PullResponse, PushNotAllowedError, PushResponse, RateLimitError, ReferenceData, RepoNotFoundError, StalenessResult, SyncError, SyncUnavailableError, canPushToWeb, checkRemote, checkRemoteByName, checkStaleness, fetchGitHubMetadata, fetchRepoStars, pullReference, pullReferenceByName, pushReference, validatePushAllowed };
|
|
175
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/sync/client.ts","../../src/sync.ts"],"mappings":";;;;cAEa,oBAAA,SAA6B,KAAA;cAExC,OAAA;AAAA;;;cCmCW,SAAA,SAAkB,KAAA;cAClB,OAAA;AAAA;AAAA,cAMA,YAAA,SAAqB,SAAA;EAAA,SAGhB,UAAA;cADhB,OAAA,UACgB,UAAA;AAAA;AAAA,cAOL,mBAAA,SAA4B,SAAA;cAC5B,OAAA;AAAA;AAAA,cAMA,cAAA,SAAuB,SAAA;cACvB,OAAA;AAAA;AAAA,cAMA,aAAA,SAAsB,SAAA;EAAA,SAGjB,eAAA;cADhB,OAAA,WACgB,eAAA;AAAA;AAAA,cAOL,iBAAA,SAA0B,SAAA;cAC1B,OAAA;AAAA;AAAA,cAMA,iBAAA,SAA0B,SAAA;cAC1B,OAAA;AAAA;AAAA,cAMA,qBAAA,SAA8B,SAAA;cAC9B,OAAA;AAAA;AAAA,cAMA,iBAAA,SAA0B,SAAA;cAC1B,OAAA;AAAA;AAAA,cAMA,aAAA,SAAsB,SAAA;cACtB,OAAA;AAAA;AAAA,cAMA,gBAAA,SAAyB,SAAA;cACzB,OAAA;AAAA;AAAA,cAMA,mBAAA,SAA4B,SAAA;cAC5B,OAAA;AAAA;AAAA,cAMA,WAAA,SAAoB,SAAA;cACpB,OAAA;AAAA;AAAA,cAMA,mBAAA,SAA4B,SAAA;EAAA,SAGvB,MAAA;cADhB,OAAA,UACgB,MAAA;AAAA;;UAQD,aAAA;EAChB,QAAA;EACA,aAAA;EACA,oBAAA;EACA,gBAAA;EACA,SAAA;EACA,WAAA;AAAA;;UAIgB,YAAA;EAChB,QAAA;EACA,aAAA;EACA,oBAAA;EACA,gBAAA;EACA,SAAA;EACA,WAAA;AAAA;;UAegB,aAAA;EAChB,MAAA;EACA,SAAA;EACA,WAAA;AAAA;AA9FD;AAAA,UAkGiB,YAAA;EAChB,OAAA;EACA,OAAA;AAAA;;UAIgB,eAAA;EAChB,OAAA;EACA,cAAA;EACA,eAAA;AAAA;;UAIgB,aAAA;EAChB,OAAA;EACA,MAAA;EACA,KAAA;AAAA;;AApGD;;;;iBAgHsB,aAAA,CAAc,QAAA,WAAmB,OAAA,CAAQ,YAAA;;;;;AAzG/D;;iBAwIsB,mBAAA,CACrB,QAAA,UACA,aAAA,WACE,OAAA,CAAQ,YAAA;;;;;;;AApIX;iBA4JsB,aAAA,CACrB,SAAA,EAAW,aAAA,EACX,KAAA,WACE,OAAA,CAAQ,YAAA;;;;;;iBAsDW,WAAA,CAAY,QAAA,WAAmB,OAAA,CAAQ,aAAA;;AA9M7D;;;;;iBA8OsB,iBAAA,CACrB,QAAA,UACA,aAAA,WACE,OAAA,CAAQ,aAAA;;;;AA1OX;;;iBAoQsB,cAAA,CACrB,QAAA,UACA,cAAA,WACE,OAAA,CAAQ,eAAA;;UAmBM,kBAAA;EAChB,KAAA;EACA,WAAA;EACA,QAAA;EACA,aAAA;AAAA;;;;;;;iBASqB,mBAAA,CACrB,KAAA,UACA,IAAA,WACE,OAAA,CAAQ,kBAAA;;;AAxRX;;;;iBA6TsB,cAAA,CAAe,KAAA,UAAe,IAAA,WAAe,OAAA;;;;;;;;iBAYnD,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,aAAA;;;;;;;iBA8BlC,mBAAA,CAAoB,MAAA,EAAQ,UAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "../config-DW8J4gl5.mjs";
|
|
2
|
+
import { C as pushReference, S as pullReferenceByName, T as SyncUnavailableError, _ as checkRemoteByName, a as GitHubError, b as fetchRepoStars, c as LowStarsError, d as PushNotAllowedError, f as RateLimitError, g as checkRemote, h as canPushToWeb, i as ConflictError, l as NetworkError, m as SyncError, n as CommitExistsError, o as InvalidInputError, p as RepoNotFoundError, r as CommitNotFoundError, s as InvalidReferenceError, t as AuthenticationError, u as PrivateRepoError, v as checkStaleness, w as validatePushAllowed, x as pullReference, y as fetchGitHubMetadata } from "../sync-wcy5fJRb.mjs";
|
|
3
|
+
|
|
4
|
+
export { AuthenticationError, CommitExistsError, CommitNotFoundError, ConflictError, GitHubError, InvalidInputError, InvalidReferenceError, LowStarsError, NetworkError, PrivateRepoError, PushNotAllowedError, RateLimitError, RepoNotFoundError, SyncError, SyncUnavailableError, canPushToWeb, checkRemote, checkRemoteByName, checkStaleness, fetchGitHubMetadata, fetchRepoStars, pullReference, pullReferenceByName, pushReference, validatePushAllowed };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./config-DW8J4gl5.mjs";
|
|
2
|
+
import { C as pushReference, S as pullReferenceByName, T as SyncUnavailableError, _ as checkRemoteByName, a as GitHubError, b as fetchRepoStars, c as LowStarsError, d as PushNotAllowedError, f as RateLimitError, g as checkRemote, h as canPushToWeb, i as ConflictError, l as NetworkError, m as SyncError, n as CommitExistsError, o as InvalidInputError, p as RepoNotFoundError, r as CommitNotFoundError, s as InvalidReferenceError, t as AuthenticationError, u as PrivateRepoError, v as checkStaleness, w as validatePushAllowed, x as pullReference, y as fetchGitHubMetadata } from "./sync-wcy5fJRb.mjs";
|
|
3
|
+
|
|
4
|
+
export { checkRemote };
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { d as toReferenceName } from "./config-DW8J4gl5.mjs";
|
|
2
|
+
import { GitHubRepoMetadataSchema } from "@offworld/types";
|
|
3
|
+
|
|
4
|
+
//#region src/sync/client.ts
|
|
5
|
+
var SyncUnavailableError = class extends Error {
|
|
6
|
+
constructor(message = "Sync requires the 'convex' package. Install it to use @offworld/sdk/sync.") {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SyncUnavailableError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
async function loadConvexModule() {
|
|
12
|
+
try {
|
|
13
|
+
return await import("convex/browser");
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error?.code === "ERR_MODULE_NOT_FOUND") throw new SyncUnavailableError();
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function getConvexClient(url, token) {
|
|
20
|
+
const { ConvexHttpClient } = await loadConvexModule();
|
|
21
|
+
const client = new ConvexHttpClient(url);
|
|
22
|
+
if (token) client.setAuth(token);
|
|
23
|
+
return client;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/sync.ts
|
|
28
|
+
/**
|
|
29
|
+
* Sync utilities for CLI-Convex communication
|
|
30
|
+
* Uses ConvexHttpClient for direct type-safe API calls
|
|
31
|
+
*/
|
|
32
|
+
let cachedApi = null;
|
|
33
|
+
async function getApi() {
|
|
34
|
+
if (cachedApi) return cachedApi;
|
|
35
|
+
try {
|
|
36
|
+
const { api } = await import("@offworld/sdk/convex/api");
|
|
37
|
+
cachedApi = api;
|
|
38
|
+
return api;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error?.code === "ERR_MODULE_NOT_FOUND") throw new SyncUnavailableError("Convex API types not found. Run `bun run build` to generate SDK artifacts.");
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const PRODUCTION_CONVEX_URL = "https://trustworthy-coyote-128.convex.cloud";
|
|
45
|
+
function getConvexUrl() {
|
|
46
|
+
return process.env.CONVEX_URL ?? PRODUCTION_CONVEX_URL;
|
|
47
|
+
}
|
|
48
|
+
const GITHUB_API_BASE = "https://api.github.com";
|
|
49
|
+
var SyncError = class extends Error {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "SyncError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var NetworkError = class extends SyncError {
|
|
56
|
+
constructor(message, statusCode) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.statusCode = statusCode;
|
|
59
|
+
this.name = "NetworkError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var AuthenticationError = class extends SyncError {
|
|
63
|
+
constructor(message = "Authentication required. Please run 'ow auth login' first.") {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = "AuthenticationError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var RateLimitError = class extends SyncError {
|
|
69
|
+
constructor(message = "Rate limit exceeded. You can push up to 3 times per repo per day.") {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = "RateLimitError";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var ConflictError = class extends SyncError {
|
|
75
|
+
constructor(message = "A newer reference already exists on the server.", remoteCommitSha) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.remoteCommitSha = remoteCommitSha;
|
|
78
|
+
this.name = "ConflictError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var CommitExistsError = class extends SyncError {
|
|
82
|
+
constructor(message = "A reference already exists for this commit SHA.") {
|
|
83
|
+
super(message);
|
|
84
|
+
this.name = "CommitExistsError";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var InvalidInputError = class extends SyncError {
|
|
88
|
+
constructor(message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.name = "InvalidInputError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var InvalidReferenceError = class extends SyncError {
|
|
94
|
+
constructor(message) {
|
|
95
|
+
super(message);
|
|
96
|
+
this.name = "InvalidReferenceError";
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var RepoNotFoundError = class extends SyncError {
|
|
100
|
+
constructor(message = "Repository not found on GitHub.") {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "RepoNotFoundError";
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var LowStarsError = class extends SyncError {
|
|
106
|
+
constructor(message = "Repository has less than 5 stars.") {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = "LowStarsError";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var PrivateRepoError = class extends SyncError {
|
|
112
|
+
constructor(message = "Private repositories are not supported.") {
|
|
113
|
+
super(message);
|
|
114
|
+
this.name = "PrivateRepoError";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var CommitNotFoundError = class extends SyncError {
|
|
118
|
+
constructor(message = "Commit not found in repository.") {
|
|
119
|
+
super(message);
|
|
120
|
+
this.name = "CommitNotFoundError";
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var GitHubError = class extends SyncError {
|
|
124
|
+
constructor(message = "GitHub API error.") {
|
|
125
|
+
super(message);
|
|
126
|
+
this.name = "GitHubError";
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var PushNotAllowedError = class extends SyncError {
|
|
130
|
+
constructor(message, reason) {
|
|
131
|
+
super(message);
|
|
132
|
+
this.reason = reason;
|
|
133
|
+
this.name = "PushNotAllowedError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function toPullResponse(result) {
|
|
137
|
+
return {
|
|
138
|
+
fullName: result.fullName,
|
|
139
|
+
referenceName: result.referenceName,
|
|
140
|
+
referenceDescription: result.referenceDescription,
|
|
141
|
+
referenceContent: result.referenceContent,
|
|
142
|
+
commitSha: result.commitSha,
|
|
143
|
+
generatedAt: result.generatedAt
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function createClient(token) {
|
|
147
|
+
return getConvexClient(getConvexUrl(), token);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Fetches reference from the remote server
|
|
151
|
+
* @param fullName - Repository full name (owner/repo)
|
|
152
|
+
* @returns Reference data or null if not found
|
|
153
|
+
*/
|
|
154
|
+
async function pullReference(fullName) {
|
|
155
|
+
const client = await createClient();
|
|
156
|
+
const api = await getApi();
|
|
157
|
+
try {
|
|
158
|
+
let result = await client.query(api.references.pull, {
|
|
159
|
+
fullName,
|
|
160
|
+
referenceName: toReferenceName(fullName)
|
|
161
|
+
});
|
|
162
|
+
if (!result) result = await client.query(api.references.pull, { fullName });
|
|
163
|
+
if (!result) return null;
|
|
164
|
+
client.mutation(api.references.recordPull, {
|
|
165
|
+
fullName,
|
|
166
|
+
referenceName: result.referenceName
|
|
167
|
+
}).catch(() => {});
|
|
168
|
+
return toPullResponse(result);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw new NetworkError(`Failed to pull reference: ${error instanceof Error ? error.message : error}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Fetches a specific reference by name from the remote server
|
|
175
|
+
* @param fullName - Repository full name (owner/repo)
|
|
176
|
+
* @param referenceName - Specific reference name to pull
|
|
177
|
+
* @returns Reference data or null if not found
|
|
178
|
+
*/
|
|
179
|
+
async function pullReferenceByName(fullName, referenceName) {
|
|
180
|
+
const client = await createClient();
|
|
181
|
+
const api = await getApi();
|
|
182
|
+
try {
|
|
183
|
+
const result = await client.query(api.references.pull, {
|
|
184
|
+
fullName,
|
|
185
|
+
referenceName
|
|
186
|
+
});
|
|
187
|
+
if (!result) return null;
|
|
188
|
+
client.mutation(api.references.recordPull, {
|
|
189
|
+
fullName,
|
|
190
|
+
referenceName
|
|
191
|
+
}).catch(() => {});
|
|
192
|
+
return toPullResponse(result);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new NetworkError(`Failed to pull reference: ${error instanceof Error ? error.message : error}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Pushes reference to the remote server
|
|
199
|
+
* All validation happens server-side
|
|
200
|
+
* @param reference - Reference data to push
|
|
201
|
+
* @param token - Authentication token
|
|
202
|
+
* @returns Push result
|
|
203
|
+
*/
|
|
204
|
+
async function pushReference(reference, token) {
|
|
205
|
+
const client = await createClient(token);
|
|
206
|
+
const api = await getApi();
|
|
207
|
+
try {
|
|
208
|
+
const result = await client.action(api.references.push, {
|
|
209
|
+
fullName: reference.fullName,
|
|
210
|
+
referenceName: reference.referenceName,
|
|
211
|
+
referenceDescription: reference.referenceDescription,
|
|
212
|
+
referenceContent: reference.referenceContent,
|
|
213
|
+
commitSha: reference.commitSha,
|
|
214
|
+
generatedAt: reference.generatedAt
|
|
215
|
+
});
|
|
216
|
+
if (!result.success) switch (result.error) {
|
|
217
|
+
case "auth_required": throw new AuthenticationError();
|
|
218
|
+
case "rate_limit": throw new RateLimitError("Rate limit exceeded. You can push up to 20 times per day.");
|
|
219
|
+
case "commit_already_exists": throw new CommitExistsError(result.message);
|
|
220
|
+
case "invalid_input": throw new InvalidInputError(result.message ?? "Invalid input");
|
|
221
|
+
case "invalid_reference": throw new InvalidReferenceError(result.message ?? "Invalid reference content");
|
|
222
|
+
case "repo_not_found": throw new RepoNotFoundError(result.message);
|
|
223
|
+
case "low_stars": throw new LowStarsError(result.message);
|
|
224
|
+
case "private_repo": throw new PrivateRepoError(result.message);
|
|
225
|
+
case "commit_not_found": throw new CommitNotFoundError(result.message);
|
|
226
|
+
case "github_error": throw new GitHubError(result.message);
|
|
227
|
+
default: throw new SyncError(result.message ?? "Unknown error");
|
|
228
|
+
}
|
|
229
|
+
return { success: true };
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (error instanceof SyncError) throw error;
|
|
232
|
+
throw new NetworkError(`Failed to push reference: ${error instanceof Error ? error.message : error}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Checks if reference exists on remote server (lightweight check)
|
|
237
|
+
* @param fullName - Repository full name (owner/repo)
|
|
238
|
+
* @returns Check result
|
|
239
|
+
*/
|
|
240
|
+
async function checkRemote(fullName) {
|
|
241
|
+
const client = await createClient();
|
|
242
|
+
const api = await getApi();
|
|
243
|
+
try {
|
|
244
|
+
let result = await client.query(api.references.check, {
|
|
245
|
+
fullName,
|
|
246
|
+
referenceName: toReferenceName(fullName)
|
|
247
|
+
});
|
|
248
|
+
if (!result.exists) result = await client.query(api.references.check, { fullName });
|
|
249
|
+
if (!result.exists) return { exists: false };
|
|
250
|
+
return {
|
|
251
|
+
exists: true,
|
|
252
|
+
commitSha: result.commitSha,
|
|
253
|
+
generatedAt: result.generatedAt
|
|
254
|
+
};
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw new NetworkError(`Failed to check remote: ${error instanceof Error ? error.message : error}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Checks if a specific reference exists on the remote server
|
|
261
|
+
* @param fullName - Repository full name (owner/repo)
|
|
262
|
+
* @param referenceName - Specific reference name to check
|
|
263
|
+
* @returns Check result
|
|
264
|
+
*/
|
|
265
|
+
async function checkRemoteByName(fullName, referenceName) {
|
|
266
|
+
const client = await createClient();
|
|
267
|
+
const api = await getApi();
|
|
268
|
+
try {
|
|
269
|
+
const result = await client.query(api.references.check, {
|
|
270
|
+
fullName,
|
|
271
|
+
referenceName
|
|
272
|
+
});
|
|
273
|
+
if (!result.exists) return { exists: false };
|
|
274
|
+
return {
|
|
275
|
+
exists: true,
|
|
276
|
+
commitSha: result.commitSha,
|
|
277
|
+
generatedAt: result.generatedAt
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
throw new NetworkError(`Failed to check remote: ${error instanceof Error ? error.message : error}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Compares local vs remote commit SHA to check staleness
|
|
285
|
+
* @param fullName - Repository full name (owner/repo)
|
|
286
|
+
* @param localCommitSha - Local commit SHA
|
|
287
|
+
* @returns Staleness result
|
|
288
|
+
*/
|
|
289
|
+
async function checkStaleness(fullName, localCommitSha) {
|
|
290
|
+
const remote = await checkRemote(fullName);
|
|
291
|
+
if (!remote.exists || !remote.commitSha) return {
|
|
292
|
+
isStale: false,
|
|
293
|
+
localCommitSha,
|
|
294
|
+
remoteCommitSha: void 0
|
|
295
|
+
};
|
|
296
|
+
return {
|
|
297
|
+
isStale: localCommitSha !== remote.commitSha,
|
|
298
|
+
localCommitSha,
|
|
299
|
+
remoteCommitSha: remote.commitSha
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Fetches GitHub repository metadata
|
|
304
|
+
* @param owner - Repository owner
|
|
305
|
+
* @param repo - Repository name
|
|
306
|
+
* @returns Repository metadata or null on error
|
|
307
|
+
*/
|
|
308
|
+
async function fetchGitHubMetadata(owner, repo) {
|
|
309
|
+
try {
|
|
310
|
+
const response = await fetch(`${GITHUB_API_BASE}/repos/${owner}/${repo}`, { headers: {
|
|
311
|
+
Accept: "application/vnd.github.v3+json",
|
|
312
|
+
"User-Agent": "offworld-cli"
|
|
313
|
+
} });
|
|
314
|
+
if (!response.ok) return null;
|
|
315
|
+
const json = await response.json();
|
|
316
|
+
const result = GitHubRepoMetadataSchema.safeParse(json);
|
|
317
|
+
if (!result.success) return null;
|
|
318
|
+
const data = result.data;
|
|
319
|
+
return {
|
|
320
|
+
stars: data.stargazers_count ?? 0,
|
|
321
|
+
description: data.description ?? void 0,
|
|
322
|
+
language: data.language ?? void 0,
|
|
323
|
+
defaultBranch: data.default_branch ?? "main"
|
|
324
|
+
};
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Fetches GitHub repository stars
|
|
331
|
+
* @param owner - Repository owner
|
|
332
|
+
* @param repo - Repository name
|
|
333
|
+
* @returns Number of stars, or 0 on error
|
|
334
|
+
*/
|
|
335
|
+
async function fetchRepoStars(owner, repo) {
|
|
336
|
+
return (await fetchGitHubMetadata(owner, repo))?.stars ?? 0;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Checks if a repository can be pushed to offworld.sh (client-side quick checks)
|
|
340
|
+
* Note: Star count and other validations happen server-side
|
|
341
|
+
*
|
|
342
|
+
* @param source - Repository source
|
|
343
|
+
* @returns Can push result
|
|
344
|
+
*/
|
|
345
|
+
function canPushToWeb(source) {
|
|
346
|
+
if (source.type === "local") return {
|
|
347
|
+
allowed: false,
|
|
348
|
+
reason: "Local repositories cannot be pushed to offworld.sh. Only remote repositories with a public URL are supported."
|
|
349
|
+
};
|
|
350
|
+
if (source.provider !== "github") return {
|
|
351
|
+
allowed: false,
|
|
352
|
+
reason: `${source.provider} repositories are not yet supported. GitHub support only for now - GitLab and Bitbucket coming soon!`
|
|
353
|
+
};
|
|
354
|
+
return { allowed: true };
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Validates that a source can be pushed and throws appropriate error if not
|
|
358
|
+
* Note: This only does quick client-side checks. Full validation happens server-side.
|
|
359
|
+
* @param source - Repository source
|
|
360
|
+
* @throws PushNotAllowedError if push is not allowed
|
|
361
|
+
*/
|
|
362
|
+
function validatePushAllowed(source) {
|
|
363
|
+
const result = canPushToWeb(source);
|
|
364
|
+
if (!result.allowed) {
|
|
365
|
+
const reason = source.type === "local" ? "local" : "not-github";
|
|
366
|
+
throw new PushNotAllowedError(result.reason, reason);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
//#endregion
|
|
371
|
+
export { pushReference as C, pullReferenceByName as S, SyncUnavailableError as T, checkRemoteByName as _, GitHubError as a, fetchRepoStars as b, LowStarsError as c, PushNotAllowedError as d, RateLimitError as f, checkRemote as g, canPushToWeb as h, ConflictError as i, NetworkError as l, SyncError as m, CommitExistsError as n, InvalidInputError as o, RepoNotFoundError as p, CommitNotFoundError as r, InvalidReferenceError as s, AuthenticationError as t, PrivateRepoError as u, checkStaleness as v, validatePushAllowed as w, pullReference as x, fetchGitHubMetadata as y };
|
|
372
|
+
//# sourceMappingURL=sync-wcy5fJRb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-wcy5fJRb.mjs","names":[],"sources":["../src/sync/client.ts","../src/sync.ts"],"sourcesContent":["import type { ConvexHttpClient } from \"convex/browser\";\n\nexport class SyncUnavailableError extends Error {\n\tconstructor(\n\t\tmessage = \"Sync requires the 'convex' package. Install it to use @offworld/sdk/sync.\",\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SyncUnavailableError\";\n\t}\n}\n\nasync function loadConvexModule(): Promise<typeof import(\"convex/browser\")> {\n\ttry {\n\t\treturn await import(\"convex/browser\");\n\t} catch (error) {\n\t\tconst err = error as { code?: string };\n\t\tif (err?.code === \"ERR_MODULE_NOT_FOUND\") {\n\t\t\tthrow new SyncUnavailableError();\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nexport async function getConvexClient(url: string, token?: string): Promise<ConvexHttpClient> {\n\tconst { ConvexHttpClient } = await loadConvexModule();\n\tconst client = new ConvexHttpClient(url);\n\tif (token) client.setAuth(token);\n\treturn client;\n}\n","/**\n * Sync utilities for CLI-Convex communication\n * Uses ConvexHttpClient for direct type-safe API calls\n */\n\nimport { toReferenceName } from \"./config.js\";\nimport { GitHubRepoMetadataSchema, type RepoSource } from \"@offworld/types\";\nimport { getConvexClient, SyncUnavailableError } from \"./sync/client.js\";\n\ntype ConvexApi = typeof import(\"@offworld/sdk/convex/api\").api;\n\nlet cachedApi: ConvexApi | null = null;\n\nasync function getApi(): Promise<ConvexApi> {\n\tif (cachedApi) return cachedApi;\n\ttry {\n\t\tconst { api } = await import(\"@offworld/sdk/convex/api\");\n\t\tcachedApi = api;\n\t\treturn api;\n\t} catch (error) {\n\t\tconst err = error as { code?: string };\n\t\tif (err?.code === \"ERR_MODULE_NOT_FOUND\") {\n\t\t\tthrow new SyncUnavailableError(\n\t\t\t\t\"Convex API types not found. Run `bun run build` to generate SDK artifacts.\",\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n// Production Convex URL - dev can override via CONVEX_URL env var\nconst PRODUCTION_CONVEX_URL = \"https://trustworthy-coyote-128.convex.cloud\";\n\nfunction getConvexUrl(): string {\n\treturn process.env.CONVEX_URL ?? PRODUCTION_CONVEX_URL;\n}\n\nconst GITHUB_API_BASE = \"https://api.github.com\";\n\nexport class SyncError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"SyncError\";\n\t}\n}\n\nexport class NetworkError extends SyncError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly statusCode?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"NetworkError\";\n\t}\n}\n\nexport class AuthenticationError extends SyncError {\n\tconstructor(message = \"Authentication required. Please run 'ow auth login' first.\") {\n\t\tsuper(message);\n\t\tthis.name = \"AuthenticationError\";\n\t}\n}\n\nexport class RateLimitError extends SyncError {\n\tconstructor(message = \"Rate limit exceeded. You can push up to 3 times per repo per day.\") {\n\t\tsuper(message);\n\t\tthis.name = \"RateLimitError\";\n\t}\n}\n\nexport class ConflictError extends SyncError {\n\tconstructor(\n\t\tmessage = \"A newer reference already exists on the server.\",\n\t\tpublic readonly remoteCommitSha?: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ConflictError\";\n\t}\n}\n\nexport class CommitExistsError extends SyncError {\n\tconstructor(message = \"A reference already exists for this commit SHA.\") {\n\t\tsuper(message);\n\t\tthis.name = \"CommitExistsError\";\n\t}\n}\n\nexport class InvalidInputError extends SyncError {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"InvalidInputError\";\n\t}\n}\n\nexport class InvalidReferenceError extends SyncError {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"InvalidReferenceError\";\n\t}\n}\n\nexport class RepoNotFoundError extends SyncError {\n\tconstructor(message = \"Repository not found on GitHub.\") {\n\t\tsuper(message);\n\t\tthis.name = \"RepoNotFoundError\";\n\t}\n}\n\nexport class LowStarsError extends SyncError {\n\tconstructor(message = \"Repository has less than 5 stars.\") {\n\t\tsuper(message);\n\t\tthis.name = \"LowStarsError\";\n\t}\n}\n\nexport class PrivateRepoError extends SyncError {\n\tconstructor(message = \"Private repositories are not supported.\") {\n\t\tsuper(message);\n\t\tthis.name = \"PrivateRepoError\";\n\t}\n}\n\nexport class CommitNotFoundError extends SyncError {\n\tconstructor(message = \"Commit not found in repository.\") {\n\t\tsuper(message);\n\t\tthis.name = \"CommitNotFoundError\";\n\t}\n}\n\nexport class GitHubError extends SyncError {\n\tconstructor(message = \"GitHub API error.\") {\n\t\tsuper(message);\n\t\tthis.name = \"GitHubError\";\n\t}\n}\n\nexport class PushNotAllowedError extends SyncError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly reason: \"local\" | \"not-github\",\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"PushNotAllowedError\";\n\t}\n}\n\n/** Reference data structure for sync operations */\nexport interface ReferenceData {\n\tfullName: string;\n\treferenceName: string;\n\treferenceDescription: string;\n\treferenceContent: string;\n\tcommitSha: string;\n\tgeneratedAt: string;\n}\n\n/** Response from pull query */\nexport interface PullResponse {\n\tfullName: string;\n\treferenceName: string;\n\treferenceDescription: string;\n\treferenceContent: string;\n\tcommitSha: string;\n\tgeneratedAt: string;\n}\n\nfunction toPullResponse(result: PullResponse): PullResponse {\n\treturn {\n\t\tfullName: result.fullName,\n\t\treferenceName: result.referenceName,\n\t\treferenceDescription: result.referenceDescription,\n\t\treferenceContent: result.referenceContent,\n\t\tcommitSha: result.commitSha,\n\t\tgeneratedAt: result.generatedAt,\n\t};\n}\n\n/** Response from check query */\nexport interface CheckResponse {\n\texists: boolean;\n\tcommitSha?: string;\n\tgeneratedAt?: string;\n}\n\n/** Response from push mutation */\nexport interface PushResponse {\n\tsuccess: boolean;\n\tmessage?: string;\n}\n\n/** Staleness check result */\nexport interface StalenessResult {\n\tisStale: boolean;\n\tlocalCommitSha?: string;\n\tremoteCommitSha?: string;\n}\n\n/** Can push result */\nexport interface CanPushResult {\n\tallowed: boolean;\n\treason?: string;\n\tstars?: number;\n}\n\nasync function createClient(token?: string) {\n\treturn getConvexClient(getConvexUrl(), token);\n}\n\n/**\n * Fetches reference from the remote server\n * @param fullName - Repository full name (owner/repo)\n * @returns Reference data or null if not found\n */\nexport async function pullReference(fullName: string): Promise<PullResponse | null> {\n\tconst client = await createClient();\n\tconst api = await getApi();\n\ttry {\n\t\tlet result = await client.query(api.references.pull, {\n\t\t\tfullName,\n\t\t\treferenceName: toReferenceName(fullName),\n\t\t});\n\t\tif (!result) {\n\t\t\tresult = await client.query(api.references.pull, { fullName });\n\t\t}\n\t\tif (!result) return null;\n\n\t\tclient\n\t\t\t.mutation(api.references.recordPull, { fullName, referenceName: result.referenceName })\n\t\t\t.catch(() => {});\n\n\t\treturn toPullResponse(result);\n\t} catch (error) {\n\t\tthrow new NetworkError(\n\t\t\t`Failed to pull reference: ${error instanceof Error ? error.message : error}`,\n\t\t);\n\t}\n}\n\n/**\n * Fetches a specific reference by name from the remote server\n * @param fullName - Repository full name (owner/repo)\n * @param referenceName - Specific reference name to pull\n * @returns Reference data or null if not found\n */\nexport async function pullReferenceByName(\n\tfullName: string,\n\treferenceName: string,\n): Promise<PullResponse | null> {\n\tconst client = await createClient();\n\tconst api = await getApi();\n\ttry {\n\t\tconst result = await client.query(api.references.pull, { fullName, referenceName });\n\t\tif (!result) return null;\n\n\t\tclient.mutation(api.references.recordPull, { fullName, referenceName }).catch(() => {});\n\n\t\treturn toPullResponse(result);\n\t} catch (error) {\n\t\tthrow new NetworkError(\n\t\t\t`Failed to pull reference: ${error instanceof Error ? error.message : error}`,\n\t\t);\n\t}\n}\n\n/**\n * Pushes reference to the remote server\n * All validation happens server-side\n * @param reference - Reference data to push\n * @param token - Authentication token\n * @returns Push result\n */\nexport async function pushReference(\n\treference: ReferenceData,\n\ttoken: string,\n): Promise<PushResponse> {\n\tconst client = await createClient(token);\n\tconst api = await getApi();\n\ttry {\n\t\tconst result = await client.action(api.references.push, {\n\t\t\tfullName: reference.fullName,\n\t\t\treferenceName: reference.referenceName,\n\t\t\treferenceDescription: reference.referenceDescription,\n\t\t\treferenceContent: reference.referenceContent,\n\t\t\tcommitSha: reference.commitSha,\n\t\t\tgeneratedAt: reference.generatedAt,\n\t\t});\n\n\t\tif (!result.success) {\n\t\t\tswitch (result.error) {\n\t\t\t\tcase \"auth_required\":\n\t\t\t\t\tthrow new AuthenticationError();\n\t\t\t\tcase \"rate_limit\":\n\t\t\t\t\tthrow new RateLimitError(\"Rate limit exceeded. You can push up to 20 times per day.\");\n\t\t\t\tcase \"commit_already_exists\":\n\t\t\t\t\tthrow new CommitExistsError(result.message);\n\t\t\t\tcase \"invalid_input\":\n\t\t\t\t\tthrow new InvalidInputError(result.message ?? \"Invalid input\");\n\t\t\t\tcase \"invalid_reference\":\n\t\t\t\t\tthrow new InvalidReferenceError(result.message ?? \"Invalid reference content\");\n\t\t\t\tcase \"repo_not_found\":\n\t\t\t\t\tthrow new RepoNotFoundError(result.message);\n\t\t\t\tcase \"low_stars\":\n\t\t\t\t\tthrow new LowStarsError(result.message);\n\t\t\t\tcase \"private_repo\":\n\t\t\t\t\tthrow new PrivateRepoError(result.message);\n\t\t\t\tcase \"commit_not_found\":\n\t\t\t\t\tthrow new CommitNotFoundError(result.message);\n\t\t\t\tcase \"github_error\":\n\t\t\t\t\tthrow new GitHubError(result.message);\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new SyncError(result.message ?? \"Unknown error\");\n\t\t\t}\n\t\t}\n\n\t\treturn { success: true };\n\t} catch (error) {\n\t\tif (error instanceof SyncError) throw error;\n\t\tthrow new NetworkError(\n\t\t\t`Failed to push reference: ${error instanceof Error ? error.message : error}`,\n\t\t);\n\t}\n}\n\n/**\n * Checks if reference exists on remote server (lightweight check)\n * @param fullName - Repository full name (owner/repo)\n * @returns Check result\n */\nexport async function checkRemote(fullName: string): Promise<CheckResponse> {\n\tconst client = await createClient();\n\tconst api = await getApi();\n\ttry {\n\t\tlet result = await client.query(api.references.check, {\n\t\t\tfullName,\n\t\t\treferenceName: toReferenceName(fullName),\n\t\t});\n\t\tif (!result.exists) {\n\t\t\tresult = await client.query(api.references.check, { fullName });\n\t\t}\n\t\tif (!result.exists) {\n\t\t\treturn { exists: false };\n\t\t}\n\t\treturn {\n\t\t\texists: true,\n\t\t\tcommitSha: result.commitSha,\n\t\t\tgeneratedAt: result.generatedAt,\n\t\t};\n\t} catch (error) {\n\t\tthrow new NetworkError(\n\t\t\t`Failed to check remote: ${error instanceof Error ? error.message : error}`,\n\t\t);\n\t}\n}\n\n/**\n * Checks if a specific reference exists on the remote server\n * @param fullName - Repository full name (owner/repo)\n * @param referenceName - Specific reference name to check\n * @returns Check result\n */\nexport async function checkRemoteByName(\n\tfullName: string,\n\treferenceName: string,\n): Promise<CheckResponse> {\n\tconst client = await createClient();\n\tconst api = await getApi();\n\ttry {\n\t\tconst result = await client.query(api.references.check, { fullName, referenceName });\n\t\tif (!result.exists) {\n\t\t\treturn { exists: false };\n\t\t}\n\t\treturn {\n\t\t\texists: true,\n\t\t\tcommitSha: result.commitSha,\n\t\t\tgeneratedAt: result.generatedAt,\n\t\t};\n\t} catch (error) {\n\t\tthrow new NetworkError(\n\t\t\t`Failed to check remote: ${error instanceof Error ? error.message : error}`,\n\t\t);\n\t}\n}\n\n/**\n * Compares local vs remote commit SHA to check staleness\n * @param fullName - Repository full name (owner/repo)\n * @param localCommitSha - Local commit SHA\n * @returns Staleness result\n */\nexport async function checkStaleness(\n\tfullName: string,\n\tlocalCommitSha: string,\n): Promise<StalenessResult> {\n\tconst remote = await checkRemote(fullName);\n\n\tif (!remote.exists || !remote.commitSha) {\n\t\treturn {\n\t\t\tisStale: false,\n\t\t\tlocalCommitSha,\n\t\t\tremoteCommitSha: undefined,\n\t\t};\n\t}\n\n\treturn {\n\t\tisStale: localCommitSha !== remote.commitSha,\n\t\tlocalCommitSha,\n\t\tremoteCommitSha: remote.commitSha,\n\t};\n}\n\n/** GitHub repository metadata */\nexport interface GitHubRepoMetadata {\n\tstars: number;\n\tdescription?: string;\n\tlanguage?: string;\n\tdefaultBranch: string;\n}\n\n/**\n * Fetches GitHub repository metadata\n * @param owner - Repository owner\n * @param repo - Repository name\n * @returns Repository metadata or null on error\n */\nexport async function fetchGitHubMetadata(\n\towner: string,\n\trepo: string,\n): Promise<GitHubRepoMetadata | null> {\n\ttry {\n\t\tconst response = await fetch(`${GITHUB_API_BASE}/repos/${owner}/${repo}`, {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github.v3+json\",\n\t\t\t\t\"User-Agent\": \"offworld-cli\",\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst json = await response.json();\n\t\tconst result = GitHubRepoMetadataSchema.safeParse(json);\n\t\tif (!result.success) {\n\t\t\treturn null;\n\t\t}\n\t\tconst data = result.data;\n\n\t\treturn {\n\t\t\tstars: data.stargazers_count ?? 0,\n\t\t\tdescription: data.description ?? undefined,\n\t\t\tlanguage: data.language ?? undefined,\n\t\t\tdefaultBranch: data.default_branch ?? \"main\",\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Fetches GitHub repository stars\n * @param owner - Repository owner\n * @param repo - Repository name\n * @returns Number of stars, or 0 on error\n */\nexport async function fetchRepoStars(owner: string, repo: string): Promise<number> {\n\tconst metadata = await fetchGitHubMetadata(owner, repo);\n\treturn metadata?.stars ?? 0;\n}\n\n/**\n * Checks if a repository can be pushed to offworld.sh (client-side quick checks)\n * Note: Star count and other validations happen server-side\n *\n * @param source - Repository source\n * @returns Can push result\n */\nexport function canPushToWeb(source: RepoSource): CanPushResult {\n\tif (source.type === \"local\") {\n\t\treturn {\n\t\t\tallowed: false,\n\t\t\treason:\n\t\t\t\t\"Local repositories cannot be pushed to offworld.sh. \" +\n\t\t\t\t\"Only remote repositories with a public URL are supported.\",\n\t\t};\n\t}\n\n\tif (source.provider !== \"github\") {\n\t\treturn {\n\t\t\tallowed: false,\n\t\t\treason:\n\t\t\t\t`${source.provider} repositories are not yet supported. ` +\n\t\t\t\t\"GitHub support only for now - GitLab and Bitbucket coming soon!\",\n\t\t};\n\t}\n\n\treturn {\n\t\tallowed: true,\n\t};\n}\n\n/**\n * Validates that a source can be pushed and throws appropriate error if not\n * Note: This only does quick client-side checks. Full validation happens server-side.\n * @param source - Repository source\n * @throws PushNotAllowedError if push is not allowed\n */\nexport function validatePushAllowed(source: RepoSource): void {\n\tconst result = canPushToWeb(source);\n\n\tif (!result.allowed) {\n\t\tconst reason: \"local\" | \"not-github\" = source.type === \"local\" ? \"local\" : \"not-github\";\n\n\t\tthrow new PushNotAllowedError(result.reason!, reason);\n\t}\n}\n\nexport { SyncUnavailableError } from \"./sync/client.js\";\n"],"mappings":"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"}
|