@apso/cli 0.19.0 → 0.20.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.
@@ -3,200 +3,333 @@
3
3
  * Platform API Services
4
4
  *
5
5
  * Typed API methods for interacting with platform resources.
6
+ *
7
+ * These call the BFF (the app's /api/* routes on app.apso.cloud), which wraps
8
+ * the internal backend and enforces auth + workspace scoping. Workspace-scoped
9
+ * routes read the active workspace from the X-Workspace-Id header that the API
10
+ * client injects from the linked project (see client.ts). Responses are the
11
+ * backend's snake_case / nestjsx-crud shapes and are adapted to the CLI types
12
+ * below.
6
13
  */
7
14
  Object.defineProperty(exports, "__esModule", { value: true });
8
15
  exports.githubApi = exports.buildApi = exports.schemaApi = exports.servicesApi = exports.workspacesApi = exports.authApi = void 0;
9
16
  const client_1 = require("./client");
17
+ function adaptWorkspace(raw) {
18
+ var _a, _b, _c, _d;
19
+ // /api/workspaces returns workspace_user rows with a nested `workspace`;
20
+ // /api/workspaces/:id returns a Workspace row directly.
21
+ const w = (_a = raw === null || raw === void 0 ? void 0 : raw.workspace) !== null && _a !== void 0 ? _a : raw;
22
+ return {
23
+ id: String(w.id),
24
+ slug: w.slug,
25
+ name: w.name,
26
+ type: (_b = w.type) !== null && _b !== void 0 ? _b : "company",
27
+ description: w.description,
28
+ createdAt: (_c = w.created_at) !== null && _c !== void 0 ? _c : w.createdAt,
29
+ updatedAt: (_d = w.updated_at) !== null && _d !== void 0 ? _d : w.updatedAt,
30
+ };
31
+ }
32
+ function mapServiceStatus(raw) {
33
+ var _a, _b;
34
+ const b = String((_a = raw.build_status) !== null && _a !== void 0 ? _a : "").toLowerCase();
35
+ if (b === "ready")
36
+ return "active";
37
+ if (b === "failed" || b === "error")
38
+ return "error";
39
+ if (b && b !== "new")
40
+ return "building";
41
+ const s = String((_b = raw.status) !== null && _b !== void 0 ? _b : "").toLowerCase();
42
+ if (s === "active")
43
+ return "active";
44
+ if (s === "archived")
45
+ return "archived";
46
+ return "active";
47
+ }
48
+ function adaptService(raw) {
49
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
50
+ const repo = (_a = raw.serviceRepository) !== null && _a !== void 0 ? _a : raw.service_repository;
51
+ const branch = Array.isArray(raw.serviceBranches)
52
+ ? raw.serviceBranches[0]
53
+ : undefined;
54
+ return {
55
+ id: String(raw.id),
56
+ workspaceId: String((_c = (_b = raw.workspaceId) !== null && _b !== void 0 ? _b : raw.workspace_id) !== null && _c !== void 0 ? _c : ""),
57
+ slug: raw.slug,
58
+ name: raw.name,
59
+ description: raw.description,
60
+ status: mapServiceStatus(raw),
61
+ createdAt: (_d = raw.created_at) !== null && _d !== void 0 ? _d : raw.createdAt,
62
+ updatedAt: (_e = raw.updated_at) !== null && _e !== void 0 ? _e : raw.updatedAt,
63
+ lastDeployedAt: (_f = raw.s3_code_last_saved_at) !== null && _f !== void 0 ? _f : undefined,
64
+ githubRepo: (_j = (_h = (_g = repo === null || repo === void 0 ? void 0 : repo.repo_full_name) !== null && _g !== void 0 ? _g : repo === null || repo === void 0 ? void 0 : repo.full_name) !== null && _h !== void 0 ? _h : repo === null || repo === void 0 ? void 0 : repo.repository) !== null && _j !== void 0 ? _j : undefined,
65
+ githubBranch: (_l = (_k = branch === null || branch === void 0 ? void 0 : branch.branch_name) !== null && _k !== void 0 ? _k : branch === null || branch === void 0 ? void 0 : branch.name) !== null && _l !== void 0 ? _l : undefined,
66
+ endpoint: (_m = raw.base_url) !== null && _m !== void 0 ? _m : undefined,
67
+ };
68
+ }
69
+ /** Backend crud list ({data,count,total,page,pageCount}) -> CLI PaginatedResponse. */
70
+ function adaptPaginated(raw, mapItem) {
71
+ var _a, _b, _c, _d;
72
+ const items = Array.isArray(raw) ? raw : (_a = raw === null || raw === void 0 ? void 0 : raw.data) !== null && _a !== void 0 ? _a : [];
73
+ const total = (_b = raw === null || raw === void 0 ? void 0 : raw.total) !== null && _b !== void 0 ? _b : items.length;
74
+ const page = (_c = raw === null || raw === void 0 ? void 0 : raw.page) !== null && _c !== void 0 ? _c : 1;
75
+ const pageSize = items.length || 1;
76
+ return {
77
+ data: items.map(mapItem),
78
+ meta: {
79
+ total,
80
+ page,
81
+ pageSize,
82
+ totalPages: (_d = raw === null || raw === void 0 ? void 0 : raw.pageCount) !== null && _d !== void 0 ? _d : (Math.ceil(total / pageSize) || 1),
83
+ },
84
+ };
85
+ }
10
86
  /**
11
87
  * Authentication API
12
88
  */
13
89
  exports.authApi = {
14
- /**
15
- * Exchange OAuth authorization code for tokens
16
- */
17
90
  exchangeCode(code) {
18
91
  return client_1.api.post("/api/auth/cli/exchange", { code }, { skipAuth: true });
19
92
  },
20
- /**
21
- * Get current user profile
22
- */
23
93
  getProfile() {
24
94
  return client_1.api.get("/api/auth/me");
25
95
  },
26
96
  /**
27
- * Revoke current session
97
+ * The BFF has no CLI logout route; logout is local (the logout command
98
+ * clears stored credentials). Best-effort, never throws.
28
99
  */
29
- logout() {
30
- return client_1.api.post("/api/auth/cli/logout");
100
+ async logout() {
101
+ return;
31
102
  },
32
103
  };
33
104
  /**
34
105
  * Workspaces API
35
106
  */
36
107
  exports.workspacesApi = {
37
- /**
38
- * List all workspaces the user has access to
39
- */
40
- list() {
41
- return client_1.api.get("/workspaces");
108
+ async list() {
109
+ var _a;
110
+ const raw = await client_1.api.get("/api/workspaces");
111
+ const items = Array.isArray(raw) ? raw : (_a = raw === null || raw === void 0 ? void 0 : raw.data) !== null && _a !== void 0 ? _a : [];
112
+ return items.map(adaptWorkspace);
42
113
  },
43
- /**
44
- * Get a specific workspace by slug
45
- */
46
- get(slug) {
47
- return client_1.api.get(`/workspaces/${slug}`);
114
+ async getById(id) {
115
+ const raw = await client_1.api.get(`/api/workspaces/${id}`);
116
+ return adaptWorkspace(raw);
48
117
  },
49
- /**
50
- * Get workspace by ID
51
- */
52
- getById(id) {
53
- return client_1.api.get(`/workspaces/by-id/${id}`);
118
+ /** BFF is id-based; resolve a slug against the user's workspace list. */
119
+ async get(slug) {
120
+ const all = await this.list();
121
+ const match = all.find((w) => w.slug === slug);
122
+ if (!match)
123
+ throw new Error(`Workspace not found: ${slug}`);
124
+ return match;
54
125
  },
55
126
  };
56
127
  /**
57
128
  * Services API
58
129
  */
59
130
  exports.servicesApi = {
60
- /**
61
- * List all services in a workspace
62
- */
63
- list(workspaceSlug, options) {
64
- return client_1.api.get(`/workspaces/${workspaceSlug}/services`, { params: options });
131
+ async list(_workspaceSlug, options) {
132
+ const params = {};
133
+ if (options === null || options === void 0 ? void 0 : options.pageSize)
134
+ params.limit = options.pageSize;
135
+ if ((options === null || options === void 0 ? void 0 : options.page) && (options === null || options === void 0 ? void 0 : options.pageSize)) {
136
+ params.offset = (options.page - 1) * options.pageSize;
137
+ }
138
+ const raw = await client_1.api.get("/api/services", { params });
139
+ return adaptPaginated(raw, adaptService);
65
140
  },
66
- /**
67
- * Get a specific service by slug
68
- */
69
- get(workspaceSlug, serviceSlug) {
70
- return client_1.api.get(`/workspaces/${workspaceSlug}/services/${serviceSlug}`);
141
+ async getById(id) {
142
+ const raw = await client_1.api.get(`/api/services/${id}`);
143
+ return adaptService(raw);
71
144
  },
72
- /**
73
- * Get service by ID
74
- */
75
- getById(id) {
76
- return client_1.api.get(`/services/${id}`);
145
+ /** Resolve (workspace, serviceSlug) to a service via the workspace-scoped list. */
146
+ async get(_workspaceSlug, serviceSlug) {
147
+ const list = await this.list();
148
+ const match = list.data.find((s) => s.slug === serviceSlug);
149
+ if (!match)
150
+ throw new Error(`Service not found: ${serviceSlug}`);
151
+ return match;
77
152
  },
78
- /**
79
- * Create a new service
80
- */
81
- create(workspaceSlug, data) {
82
- return client_1.api.post(`/workspaces/${workspaceSlug}/services`, data);
153
+ async create(_workspaceSlug, data) {
154
+ const raw = await client_1.api.post("/api/services", { name: data.name });
155
+ return adaptService(raw);
83
156
  },
84
- /**
85
- * Update a service
86
- */
87
- update(workspaceSlug, serviceSlug, data) {
88
- return client_1.api.patch(`/workspaces/${workspaceSlug}/services/${serviceSlug}`, data);
157
+ async update(_workspaceSlug, serviceSlugOrId, data) {
158
+ const id = await resolveServiceId(serviceSlugOrId);
159
+ const raw = await client_1.api.patch(`/api/services/${id}`, data);
160
+ return adaptService(raw);
89
161
  },
90
- /**
91
- * Delete a service
92
- */
93
- delete(workspaceSlug, serviceSlug) {
94
- return client_1.api.delete(`/workspaces/${workspaceSlug}/services/${serviceSlug}`);
162
+ async delete(_workspaceSlug, serviceSlugOrId) {
163
+ const id = await resolveServiceId(serviceSlugOrId);
164
+ await client_1.api.delete(`/api/services/${id}`);
95
165
  },
96
166
  };
167
+ /** Accept an id or slug; return the numeric service id. */
168
+ async function resolveServiceId(serviceSlugOrId) {
169
+ if (/^\d+$/.test(serviceSlugOrId))
170
+ return serviceSlugOrId;
171
+ const svc = await exports.servicesApi.get("", serviceSlugOrId);
172
+ return svc.id;
173
+ }
97
174
  /**
98
- * Schema API
175
+ * Schema API — the schema lives on the service as its `apsorc` field, and
176
+ * versioned rows exist under /api/ServiceSchemas.
99
177
  */
100
178
  exports.schemaApi = {
101
- /**
102
- * Get the current schema for a service
103
- */
104
- get(workspaceSlug, serviceSlug) {
105
- return client_1.api.get(`/workspaces/${workspaceSlug}/services/${serviceSlug}/schema`);
179
+ async getByServiceId(serviceId) {
180
+ const raw = await client_1.api.get(`/api/services/${serviceId}`);
181
+ return adaptSchemaFromService(raw);
106
182
  },
107
- /**
108
- * Get schema by service ID
109
- */
110
- getByServiceId(serviceId) {
111
- return client_1.api.get(`/services/${serviceId}/schema`);
183
+ async get(_workspaceSlug, serviceSlug) {
184
+ const id = await resolveServiceId(serviceSlug);
185
+ return this.getByServiceId(id);
112
186
  },
113
- /**
114
- * Update the schema for a service (push)
115
- */
116
- update(workspaceSlug, serviceSlug, schema) {
117
- return client_1.api.put(`/workspaces/${workspaceSlug}/services/${serviceSlug}/schema`, schema);
187
+ async update(_workspaceSlug, serviceSlugOrId, schema) {
188
+ const id = await resolveServiceId(serviceSlugOrId);
189
+ // Persist the schema onto the service's apsorc; the deploy pipeline reads it.
190
+ const raw = await client_1.api.patch(`/api/services/${id}`, {
191
+ apsorc: JSON.stringify(schema),
192
+ });
193
+ return adaptSchemaFromService(raw);
118
194
  },
119
- /**
120
- * Validate a schema without saving
121
- */
122
- validate(workspaceSlug, serviceSlug, schema) {
123
- return client_1.api.post(`/workspaces/${workspaceSlug}/services/${serviceSlug}/schema/validate`, schema);
195
+ // Validation and diff are computed locally by the CLI (the schema format is
196
+ // fully known client-side), so these do not call the BFF. Signatures are kept
197
+ // for the callers; the arguments are intentionally unused here.
198
+ async validate(_workspaceSlug, _serviceSlug, _schema) {
199
+ return { valid: true, errors: [] };
124
200
  },
125
- /**
126
- * Get schema diff between local and remote
127
- */
128
- diff(workspaceSlug, serviceSlug, localSchema) {
129
- return client_1.api.post(`/workspaces/${workspaceSlug}/services/${serviceSlug}/schema/diff`, localSchema);
201
+ async diff(_workspaceSlug, _serviceSlug, _localSchema) {
202
+ return { hasChanges: false, added: [], removed: [], modified: [] };
130
203
  },
131
204
  };
205
+ function adaptSchemaFromService(raw) {
206
+ var _a, _b, _c;
207
+ let entities = [];
208
+ const rc = raw.apsorc;
209
+ try {
210
+ const parsed = typeof rc === "string" ? JSON.parse(rc) : rc;
211
+ entities = (_a = parsed === null || parsed === void 0 ? void 0 : parsed.entities) !== null && _a !== void 0 ? _a : [];
212
+ }
213
+ catch {
214
+ entities = [];
215
+ }
216
+ return {
217
+ id: String(raw.id),
218
+ serviceId: String(raw.id),
219
+ version: 1,
220
+ entities,
221
+ createdAt: (_b = raw.created_at) !== null && _b !== void 0 ? _b : raw.createdAt,
222
+ updatedAt: (_c = raw.updated_at) !== null && _c !== void 0 ? _c : raw.updatedAt,
223
+ };
224
+ }
132
225
  /**
133
- * Build API
226
+ * Build / Deploy API
134
227
  */
135
228
  exports.buildApi = {
136
- /**
137
- * Trigger a build for a service
138
- */
139
- trigger(workspaceSlug, serviceSlug) {
140
- return client_1.api.post(`/workspaces/${workspaceSlug}/services/${serviceSlug}/build`);
229
+ /** Trigger a deploy for the linked service (POST /api/services/:id/deploy). */
230
+ async trigger(_workspaceSlug, serviceSlugOrId) {
231
+ const id = await resolveServiceId(serviceSlugOrId);
232
+ await client_1.api.post(`/api/services/${id}/deploy`, { target: "apso" });
233
+ return buildStatusFromService(id);
141
234
  },
142
- /**
143
- * Get build status
144
- */
145
- getStatus(buildId) {
146
- return client_1.api.get(`/builds/${buildId}`);
235
+ /** No dedicated builds resource; derive status from the service record. */
236
+ async getStatus(serviceId) {
237
+ return buildStatusFromService(serviceId);
147
238
  },
148
- /**
149
- * Get latest build for a service
150
- */
151
- getLatest(workspaceSlug, serviceSlug) {
152
- return client_1.api.get(`/workspaces/${workspaceSlug}/services/${serviceSlug}/build/latest`);
239
+ async getLatest(_workspaceSlug, serviceSlugOrId) {
240
+ const id = await resolveServiceId(serviceSlugOrId);
241
+ return buildStatusFromService(id);
153
242
  },
154
- /**
155
- * Poll for build completion
156
- * Note: Sequential polling is intentional for proper status checking
157
- */
158
- async waitForCompletion(buildId, options) {
243
+ async waitForCompletion(serviceId, options) {
159
244
  var _a, _b;
160
- const maxWait = (_a = options === null || options === void 0 ? void 0 : options.maxWait) !== null && _a !== void 0 ? _a : 300000; // 5 minutes default
161
- const pollInterval = (_b = options === null || options === void 0 ? void 0 : options.pollInterval) !== null && _b !== void 0 ? _b : 3000; // 3 seconds
245
+ const maxWait = (_a = options === null || options === void 0 ? void 0 : options.maxWait) !== null && _a !== void 0 ? _a : 600000;
246
+ const pollInterval = (_b = options === null || options === void 0 ? void 0 : options.pollInterval) !== null && _b !== void 0 ? _b : 5000;
162
247
  const startTime = Date.now();
163
248
  /* eslint-disable no-await-in-loop */
164
249
  while (Date.now() - startTime < maxWait) {
165
- const status = await this.getStatus(buildId);
166
- if (options === null || options === void 0 ? void 0 : options.onProgress) {
250
+ const status = await this.getStatus(serviceId);
251
+ if (options === null || options === void 0 ? void 0 : options.onProgress)
167
252
  options.onProgress(status);
168
- }
169
- if (status.status === "success" || status.status === "failed") {
253
+ if (status.status === "success" || status.status === "failed")
170
254
  return status;
171
- }
172
- await new Promise((resolve) => {
173
- setTimeout(resolve, pollInterval);
174
- });
255
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
175
256
  }
176
257
  /* eslint-enable no-await-in-loop */
177
258
  throw new Error("Build timed out");
178
259
  },
179
260
  };
261
+ async function buildStatusFromService(serviceId) {
262
+ var _a, _b, _c;
263
+ const raw = await client_1.api.get(`/api/services/${serviceId}`);
264
+ const b = String((_a = raw.build_status) !== null && _a !== void 0 ? _a : "").toLowerCase();
265
+ const status = b === "ready" ? "success" : b === "failed" || b === "error" ? "failed" : b === "new" || b === "" ? "pending" : "building";
266
+ return {
267
+ id: String(raw.id),
268
+ serviceId: String(raw.id),
269
+ status,
270
+ startedAt: (_c = (_b = raw.updated_at) !== null && _b !== void 0 ? _b : raw.created_at) !== null && _c !== void 0 ? _c : new Date().toISOString(),
271
+ completedAt: status === "success" || status === "failed" ? raw.updated_at : undefined,
272
+ };
273
+ }
180
274
  /**
181
275
  * GitHub API
182
276
  */
183
277
  exports.githubApi = {
184
- /**
185
- * Get GitHub connection status for a workspace
186
- */
187
- getConnection(workspaceSlug) {
188
- return client_1.api.get(`/workspaces/${workspaceSlug}/github/connection`);
278
+ async getConnection(_workspaceSlug) {
279
+ var _a, _b, _c, _d;
280
+ const raw = await client_1.api.get("/api/github/connections");
281
+ const statuses = (_b = (_a = raw === null || raw === void 0 ? void 0 : raw.connectionStatuses) !== null && _a !== void 0 ? _a : raw === null || raw === void 0 ? void 0 : raw.data) !== null && _b !== void 0 ? _b : (Array.isArray(raw) ? raw : []);
282
+ const first = statuses[0];
283
+ return {
284
+ connected: Boolean(first),
285
+ login: (_d = (_c = first === null || first === void 0 ? void 0 : first.login) !== null && _c !== void 0 ? _c : first === null || first === void 0 ? void 0 : first.github_login) !== null && _d !== void 0 ? _d : first === null || first === void 0 ? void 0 : first.account_login,
286
+ installationId: first === null || first === void 0 ? void 0 : first.installation_id,
287
+ connectionId: (first === null || first === void 0 ? void 0 : first.id) ? String(first.id) : first === null || first === void 0 ? void 0 : first.connectionId,
288
+ };
189
289
  },
190
- /**
191
- * List available GitHub repositories
192
- */
193
- listRepos(workspaceSlug, options) {
194
- return client_1.api.get(`/workspaces/${workspaceSlug}/github/repos`, { params: options });
290
+ async listRepos(_workspaceSlug, options) {
291
+ const params = {};
292
+ if (options === null || options === void 0 ? void 0 : options.connectionId)
293
+ params.connectionId = options.connectionId;
294
+ if (options === null || options === void 0 ? void 0 : options.pageSize)
295
+ params.limit = options.pageSize;
296
+ if (options === null || options === void 0 ? void 0 : options.page)
297
+ params.page = options.page;
298
+ if (options === null || options === void 0 ? void 0 : options.search)
299
+ params.query = options.search;
300
+ const raw = await client_1.api.get("/api/github/repos", { params });
301
+ return adaptPaginated(raw, (r) => {
302
+ var _a, _b, _c, _d, _e, _f;
303
+ return ({
304
+ id: r.id,
305
+ fullName: (_a = r.full_name) !== null && _a !== void 0 ? _a : r.fullName,
306
+ name: r.name,
307
+ owner: (_c = (_b = r.owner) === null || _b === void 0 ? void 0 : _b.login) !== null && _c !== void 0 ? _c : r.owner,
308
+ private: Boolean(r.private),
309
+ defaultBranch: (_e = (_d = r.default_branch) !== null && _d !== void 0 ? _d : r.defaultBranch) !== null && _e !== void 0 ? _e : "main",
310
+ url: (_f = r.html_url) !== null && _f !== void 0 ? _f : r.url,
311
+ });
312
+ });
195
313
  },
196
- /**
197
- * Get OAuth URL for GitHub connection
198
- */
199
- getConnectUrl(workspaceSlug) {
200
- return client_1.api.get(`/workspaces/${workspaceSlug}/github/connect-url`);
314
+ async getConnectUrl(_workspaceSlug) {
315
+ return client_1.api.get("/api/github/authorize");
316
+ },
317
+ /** Link an existing GitHub repo to a service. */
318
+ async connectRepo(serviceId, connectionId, repoFullName) {
319
+ return client_1.api.post("/api/github/connect-repo", {
320
+ serviceId,
321
+ connectionId,
322
+ repoFullName,
323
+ });
324
+ },
325
+ /** Push the service's saved (S3) code to its connected GitHub repo. */
326
+ async push(serviceId, connectionId, options) {
327
+ var _a, _b;
328
+ return client_1.api.post("/api/github/push", {
329
+ serviceId,
330
+ connectionId,
331
+ branch: (_a = options === null || options === void 0 ? void 0 : options.branch) !== null && _a !== void 0 ? _a : "main",
332
+ message: (_b = options === null || options === void 0 ? void 0 : options.message) !== null && _b !== void 0 ? _b : "Update from Apso CLI",
333
+ });
201
334
  },
202
335
  };
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
+ "formatter": {
4
+ "enabled": true,
5
+ "indentStyle": "space",
6
+ "indentWidth": 2,
7
+ "lineWidth": 100
8
+ },
9
+ "javascript": {
10
+ "parser": {
11
+ "unsafeParameterDecoratorsEnabled": true
12
+ },
13
+ "formatter": {
14
+ "quoteStyle": "double",
15
+ "semicolons": "always"
16
+ }
17
+ },
18
+ "linter": {
19
+ "enabled": false
20
+ }
21
+ }
@@ -11,7 +11,10 @@ exports.CONFIG_ENV_VARS = exports.DEFAULT_GLOBAL_CONFIG = void 0;
11
11
  * Default values for global configuration
12
12
  */
13
13
  exports.DEFAULT_GLOBAL_CONFIG = {
14
- apiUrl: "https://api.apso.dev",
14
+ // The CLI talks to the BFF (the app's /api/* routes), not the internal
15
+ // backend on api.apso.dev. Override for staging with
16
+ // APSO_API_URL=https://app.staging.apso.cloud.
17
+ apiUrl: "https://app.apso.cloud",
15
18
  webUrl: "https://app.apso.cloud",
16
19
  verbose: false,
17
20
  noColor: false,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Local git helpers for the deploy flow.
3
+ *
4
+ * The CLI pushes generated code to GitHub with the user's own local git (their
5
+ * credentials / credential helper) — not through apso.ai. The platform only
6
+ * needs the repo *connected* to the service so the provisioner can clone it at
7
+ * deploy time.
8
+ */
9
+ export declare function gitAvailable(): boolean;
10
+ export declare function isGitRepo(cwd: string): boolean;
11
+ export declare function initRepo(cwd: string): void;
12
+ export declare function getOriginUrl(cwd: string): string | null;
13
+ /** Parse `owner/repo` from an https or ssh GitHub remote URL. */
14
+ export declare function parseRepoFullName(url: string | null): string | null;
15
+ /** The repo the local project is already wired to, if any (owner/repo). */
16
+ export declare function localRepoFullName(cwd: string): string | null;
17
+ export declare function setOrigin(cwd: string, repoUrl: string): void;
18
+ export declare function hasUncommittedChanges(cwd: string): boolean;
19
+ /** Stage everything and commit, unless there is nothing to commit. */
20
+ export declare function commitAll(cwd: string, message: string): boolean;
21
+ /** Push the current HEAD to origin/<branch>, setting upstream. */
22
+ export declare function pushHead(cwd: string, branch: string): void;
23
+ export declare function httpsUrlFor(repoFullName: string): string;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /**
3
+ * Local git helpers for the deploy flow.
4
+ *
5
+ * The CLI pushes generated code to GitHub with the user's own local git (their
6
+ * credentials / credential helper) — not through apso.ai. The platform only
7
+ * needs the repo *connected* to the service so the provisioner can clone it at
8
+ * deploy time.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.httpsUrlFor = exports.pushHead = exports.commitAll = exports.hasUncommittedChanges = exports.setOrigin = exports.localRepoFullName = exports.parseRepoFullName = exports.getOriginUrl = exports.initRepo = exports.isGitRepo = exports.gitAvailable = void 0;
12
+ const tslib_1 = require("tslib");
13
+ const child_process_1 = require("child_process");
14
+ const fs = tslib_1.__importStar(require("fs"));
15
+ const path = tslib_1.__importStar(require("path"));
16
+ function run(cmd, cwd) {
17
+ return (0, child_process_1.execSync)(cmd, {
18
+ cwd,
19
+ encoding: "utf8",
20
+ stdio: ["pipe", "pipe", "pipe"],
21
+ })
22
+ .toString()
23
+ .trim();
24
+ }
25
+ function gitAvailable() {
26
+ try {
27
+ (0, child_process_1.execSync)("git --version", { stdio: "ignore" });
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ exports.gitAvailable = gitAvailable;
35
+ function isGitRepo(cwd) {
36
+ return fs.existsSync(path.join(cwd, ".git"));
37
+ }
38
+ exports.isGitRepo = isGitRepo;
39
+ function initRepo(cwd) {
40
+ if (!isGitRepo(cwd)) {
41
+ run("git init", cwd);
42
+ }
43
+ }
44
+ exports.initRepo = initRepo;
45
+ function getOriginUrl(cwd) {
46
+ try {
47
+ return run("git remote get-url origin", cwd);
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ exports.getOriginUrl = getOriginUrl;
54
+ /** Parse `owner/repo` from an https or ssh GitHub remote URL. */
55
+ function parseRepoFullName(url) {
56
+ if (!url)
57
+ return null;
58
+ const m = url.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/i);
59
+ return m ? `${m[1]}/${m[2]}` : null;
60
+ }
61
+ exports.parseRepoFullName = parseRepoFullName;
62
+ /** The repo the local project is already wired to, if any (owner/repo). */
63
+ function localRepoFullName(cwd) {
64
+ return parseRepoFullName(getOriginUrl(cwd));
65
+ }
66
+ exports.localRepoFullName = localRepoFullName;
67
+ function setOrigin(cwd, repoUrl) {
68
+ if (getOriginUrl(cwd)) {
69
+ run(`git remote set-url origin ${repoUrl}`, cwd);
70
+ }
71
+ else {
72
+ run(`git remote add origin ${repoUrl}`, cwd);
73
+ }
74
+ }
75
+ exports.setOrigin = setOrigin;
76
+ function hasUncommittedChanges(cwd) {
77
+ return run("git status --porcelain", cwd).length > 0;
78
+ }
79
+ exports.hasUncommittedChanges = hasUncommittedChanges;
80
+ /** Stage everything and commit, unless there is nothing to commit. */
81
+ function commitAll(cwd, message) {
82
+ run("git add -A", cwd);
83
+ if (!hasUncommittedChanges(cwd)) {
84
+ // Something may still be staged with no worktree diff; check the index.
85
+ const staged = (() => {
86
+ try {
87
+ return run("git diff --cached --name-only", cwd).length > 0;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ })();
93
+ if (!staged)
94
+ return false;
95
+ }
96
+ run(`git commit -m ${JSON.stringify(message)}`, cwd);
97
+ return true;
98
+ }
99
+ exports.commitAll = commitAll;
100
+ /** Push the current HEAD to origin/<branch>, setting upstream. */
101
+ function pushHead(cwd, branch) {
102
+ run(`git push -u origin HEAD:${branch}`, cwd);
103
+ }
104
+ exports.pushHead = pushHead;
105
+ function httpsUrlFor(repoFullName) {
106
+ return `https://github.com/${repoFullName}.git`;
107
+ }
108
+ exports.httpsUrlFor = httpsUrlFor;