@genex-ai/cli-demo 1.15.0-dev.577 → 1.15.1-dev.579
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.
|
@@ -4,11 +4,304 @@ import {
|
|
|
4
4
|
ENV_TOKEN_KEY,
|
|
5
5
|
c,
|
|
6
6
|
getApiUrl,
|
|
7
|
+
getAuthUrl,
|
|
7
8
|
getCliVersion,
|
|
8
9
|
getGenexEnvPath
|
|
9
10
|
} from "./chunk-HYCSNWYX.js";
|
|
10
11
|
|
|
12
|
+
// src/lib/terms.ts
|
|
13
|
+
import readline from "readline";
|
|
14
|
+
|
|
15
|
+
// src/lib/source-sync.ts
|
|
16
|
+
import fs from "fs/promises";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import os from "os";
|
|
19
|
+
|
|
20
|
+
// src/utils/run.ts
|
|
21
|
+
import { spawn } from "child_process";
|
|
22
|
+
var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
|
|
23
|
+
function run(cmd, args, env) {
|
|
24
|
+
const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
let child;
|
|
27
|
+
try {
|
|
28
|
+
child = spawn(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
|
|
29
|
+
} catch {
|
|
30
|
+
resolve({ code: -1, out: "", err: `${cmd} not found` });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let out = "";
|
|
34
|
+
let err = "";
|
|
35
|
+
child.stdout?.on("data", (d) => out += String(d));
|
|
36
|
+
child.stderr?.on("data", (d) => err += String(d));
|
|
37
|
+
child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
|
|
38
|
+
child.on("close", (code) => resolve({ code: code ?? -1, out, err }));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/lib/source-sync.ts
|
|
43
|
+
async function readRemoteSource(apiUrl, token, projectId) {
|
|
44
|
+
try {
|
|
45
|
+
const res = await apiFetch(`${apiUrl}/api/projects/${projectId}`, {
|
|
46
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) return null;
|
|
49
|
+
const data = await res.json().catch(() => null);
|
|
50
|
+
if (!data?.project) return null;
|
|
51
|
+
return {
|
|
52
|
+
stagingCommit: data.project.stagingCommitSha ?? null,
|
|
53
|
+
commit: data.project.commitSha ?? null
|
|
54
|
+
};
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isStale(local, remote) {
|
|
60
|
+
if (!local || !remote) return false;
|
|
61
|
+
return local !== remote;
|
|
62
|
+
}
|
|
63
|
+
function inHostedSession() {
|
|
64
|
+
return process.env.GENEX_HOSTED_SESSION === "1";
|
|
65
|
+
}
|
|
66
|
+
function mayForceOverAnotherDevice() {
|
|
67
|
+
return !inHostedSession();
|
|
68
|
+
}
|
|
69
|
+
function reportForceRefused(log) {
|
|
70
|
+
log.error("`--force` is not available inside a Genex chat session.");
|
|
71
|
+
log.dim(" It would replace work shipped from another machine, and that is not yours to discard here.");
|
|
72
|
+
log.dim(` Do this instead \u2014 it keeps both sides:`);
|
|
73
|
+
log.dim(` 1. ${c.cyan("npx genex pull --force")} takes the other machine's work (a copy of this folder is kept under .genex/replaced-*)`);
|
|
74
|
+
log.dim(` 2. redo your change on top of it \u2014 you know what you just changed`);
|
|
75
|
+
log.dim(` 3. ${c.cyan("npx genex preview")}`);
|
|
76
|
+
log.dim(" If the user explicitly wants their chat version to win, ask them to run `npx genex preview --force` themselves.");
|
|
77
|
+
}
|
|
78
|
+
function reportStale(log, slug, local, remote) {
|
|
79
|
+
log.error(`${c.cyan(slug)} was updated from another device \u2014 nothing was deployed.`);
|
|
80
|
+
if (remote && local) {
|
|
81
|
+
const short = remote.slice(0, 7) !== local.slice(0, 7);
|
|
82
|
+
const r = short ? remote.slice(0, 7) : remote;
|
|
83
|
+
const l = short ? local.slice(0, 7) : local;
|
|
84
|
+
log.dim(` the draft is on ${r}, this folder last shipped ${l}`);
|
|
85
|
+
}
|
|
86
|
+
log.dim(` ${c.cyan("npx genex pull")} \u2014 take the other device's work (refuses if you have unshipped changes)`);
|
|
87
|
+
log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
|
|
88
|
+
}
|
|
89
|
+
async function sourceTreeHash(cwd) {
|
|
90
|
+
const gitDir = await fs.mkdtemp(path.join(os.tmpdir(), "genex-tree-"));
|
|
91
|
+
const base = { GIT_DIR: gitDir };
|
|
92
|
+
try {
|
|
93
|
+
if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
|
|
94
|
+
await fs.writeFile(
|
|
95
|
+
path.join(gitDir, "info", "exclude"),
|
|
96
|
+
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
97
|
+
);
|
|
98
|
+
if ((await run("git", ["lfs", "version"], base)).code === 0) {
|
|
99
|
+
const filters = [
|
|
100
|
+
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
101
|
+
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
102
|
+
["filter.lfs.process", "git-lfs filter-process"],
|
|
103
|
+
["filter.lfs.required", "true"]
|
|
104
|
+
];
|
|
105
|
+
for (const [key, value] of filters) await run("git", ["config", key, value], base);
|
|
106
|
+
}
|
|
107
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path.join(gitDir, "index-tree") };
|
|
108
|
+
if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
|
|
109
|
+
const tree = (await run("git", ["write-tree"], env)).out.trim();
|
|
110
|
+
return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
} finally {
|
|
114
|
+
await fs.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function urlHasEmbeddedCredentials(url) {
|
|
119
|
+
return /^[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(url);
|
|
120
|
+
}
|
|
121
|
+
function credentialHelperOff(url) {
|
|
122
|
+
if (!urlHasEmbeddedCredentials(url)) return {};
|
|
123
|
+
return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "credential.helper", GIT_CONFIG_VALUE_0: "" };
|
|
124
|
+
}
|
|
125
|
+
async function fetchCloneGrant(apiUrl, token, projectId, log) {
|
|
126
|
+
let res;
|
|
127
|
+
try {
|
|
128
|
+
res = await apiFetch(`${apiUrl}/api/projects/${projectId}/push-token`, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { Authorization: `Bearer ${token}`, "X-Genex-Source-Intent": "read" }
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
log.error(`Couldn't reach the API to authorize the source read: ${String(err)}`);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (res.status === 401) {
|
|
137
|
+
log.error("Not authorized \u2014 your token may have expired. Re-run `genex auth`.");
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
log.error(`Couldn't authorize the source read (HTTP ${res.status}).`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const data = await res.json().catch(() => null);
|
|
145
|
+
const url = data?.pushUrl ?? data?.cloneUrl;
|
|
146
|
+
if (!url) {
|
|
147
|
+
log.error("The API didn't return a source URL.");
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return { cloneUrl: url, sourceRef: data?.sourceRef ?? null };
|
|
151
|
+
}
|
|
152
|
+
async function cloneSource(grant, dest, log) {
|
|
153
|
+
const args = grant.sourceRef ? ["clone", "--branch", grant.sourceRef, grant.cloneUrl, dest] : ["clone", grant.cloneUrl, dest];
|
|
154
|
+
const env = {
|
|
155
|
+
GIT_LFS_SKIP_SMUDGE: "1",
|
|
156
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
157
|
+
...credentialHelperOff(grant.cloneUrl)
|
|
158
|
+
};
|
|
159
|
+
const cloned = await run("git", args, env);
|
|
160
|
+
if (cloned.code !== 0) {
|
|
161
|
+
log.error("Couldn't download the game's source.");
|
|
162
|
+
log.dim(` git clone exited ${cloned.code}`);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const repo = { ...env, GIT_DIR: path.join(dest, ".git"), GIT_WORK_TREE: dest };
|
|
166
|
+
const filters = [
|
|
167
|
+
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
168
|
+
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
169
|
+
["filter.lfs.process", "git-lfs filter-process"],
|
|
170
|
+
["filter.lfs.required", "true"]
|
|
171
|
+
];
|
|
172
|
+
for (const [key, value] of filters) await run("git", ["config", key, value], repo);
|
|
173
|
+
const pulled = await run("git", ["lfs", "pull"], repo);
|
|
174
|
+
if (pulled.code !== 0) {
|
|
175
|
+
log.warn("Binary assets (models, textures, audio) are still pointer files \u2014 `git lfs pull` failed.");
|
|
176
|
+
log.dim(" The source is there; run `git lfs pull` in this folder once git-lfs works.");
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/lib/terms.ts
|
|
182
|
+
var TERMS_ERROR_CODE = "terms_acceptance_required";
|
|
183
|
+
var TERMS_WAIT_MS = 1e5;
|
|
184
|
+
var TERMS_POLL_MS = 3e3;
|
|
185
|
+
async function isTermsRefusal(res) {
|
|
186
|
+
if (res.status !== 403) return false;
|
|
187
|
+
try {
|
|
188
|
+
const body = await res.clone().json();
|
|
189
|
+
return body?.error === TERMS_ERROR_CODE;
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function termsRefusalUrl(res) {
|
|
195
|
+
try {
|
|
196
|
+
const body = await res.clone().json();
|
|
197
|
+
return typeof body?.url === "string" && body.url ? body.url : null;
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function acceptUrl(fromServer, authUrl) {
|
|
203
|
+
return fromServer || `${getAuthUrl(authUrl)}/accept`;
|
|
204
|
+
}
|
|
205
|
+
function reportTermsRefusal(log, url) {
|
|
206
|
+
log.error("Your account needs to accept the updated Terms before it can generate or publish.");
|
|
207
|
+
log.dim(" Nothing was charged, and nothing on your project changed.");
|
|
208
|
+
log.dim(` Open ${c.cyan(acceptUrl(url))} and agree \u2014 one click.`);
|
|
209
|
+
}
|
|
210
|
+
async function waitForAcceptance(opts) {
|
|
211
|
+
const timeoutMs = opts.timeoutMs ?? TERMS_WAIT_MS;
|
|
212
|
+
const intervalMs = opts.intervalMs ?? TERMS_POLL_MS;
|
|
213
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
214
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
215
|
+
const now = opts.now ?? Date.now;
|
|
216
|
+
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
217
|
+
opts.log.dim(` Waiting for you to accept (up to ${Math.round(timeoutMs / 1e3)}s)\u2026`);
|
|
218
|
+
const deadline = now() + timeoutMs;
|
|
219
|
+
let first = true;
|
|
220
|
+
while (first || now() < deadline) {
|
|
221
|
+
if (!first) await sleep(Math.min(intervalMs, Math.max(0, deadline - now())));
|
|
222
|
+
first = false;
|
|
223
|
+
try {
|
|
224
|
+
const res = await doFetch(`${base}/api/legal/status`, {
|
|
225
|
+
headers: { Authorization: `Bearer ${opts.token}` }
|
|
226
|
+
});
|
|
227
|
+
if (!res.ok) return false;
|
|
228
|
+
const body = await res.json().catch(() => null);
|
|
229
|
+
if (body?.accepted === true) {
|
|
230
|
+
opts.log.success("Accepted \u2014 continuing.");
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
opts.log.error("Still not accepted. Re-run this command once you have agreed.");
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
function askOnStdin(question) {
|
|
241
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
rl.question(question, (answer) => {
|
|
244
|
+
rl.close();
|
|
245
|
+
resolve(answer);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
async function runAccept(opts) {
|
|
250
|
+
const { token, log } = opts;
|
|
251
|
+
const interactive = opts.interactive ?? Boolean(process.stdin.isTTY);
|
|
252
|
+
const apiUrl = getApiUrl(opts.apiUrl);
|
|
253
|
+
let acceptPage = null;
|
|
254
|
+
const status = await apiFetch(`${apiUrl}/api/legal/status`, {
|
|
255
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
256
|
+
}).catch(() => null);
|
|
257
|
+
if (status?.ok) {
|
|
258
|
+
const body = await status.json().catch(() => null);
|
|
259
|
+
if (body?.accepted) {
|
|
260
|
+
log.success("Already accepted \u2014 nothing to do.");
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
acceptPage = typeof body?.acceptUrl === "string" ? body.acceptUrl : null;
|
|
264
|
+
log.plain("Before generating or publishing, please read and agree to:");
|
|
265
|
+
for (const doc of body?.documents ?? []) log.plain(` ${doc.title} ${c.cyan(doc.url)}`);
|
|
266
|
+
log.plain("");
|
|
267
|
+
}
|
|
268
|
+
if (!interactive) {
|
|
269
|
+
log.plain("Accepting the Terms needs a person \u2014 an agent cannot agree on your behalf.");
|
|
270
|
+
log.plain(` Open ${c.cyan(acceptUrl(acceptPage))} and agree \u2014 one click.`);
|
|
271
|
+
return waitForAcceptance({ apiUrl, token, log, ...opts.wait ?? {} });
|
|
272
|
+
}
|
|
273
|
+
const ask = opts.ask ?? askOnStdin;
|
|
274
|
+
const answer = (await ask("Type 'agree' to accept: ")).trim().toLowerCase();
|
|
275
|
+
if (answer !== "agree") {
|
|
276
|
+
log.error("Not accepted \u2014 nothing was recorded.");
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const res = await apiFetch(`${apiUrl}/api/legal/accept`, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
282
|
+
body: JSON.stringify({ surface: "cli" })
|
|
283
|
+
}).catch(() => null);
|
|
284
|
+
if (!res?.ok) {
|
|
285
|
+
log.error(`Couldn't record your acceptance (HTTP ${res?.status ?? "no response"}).`);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
log.success("Accepted. Re-run your command.");
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
11
292
|
// src/lib/api.ts
|
|
293
|
+
var stderrLogger = /* @__PURE__ */ (() => {
|
|
294
|
+
const err = (s) => void process.stderr.write(s + "\n");
|
|
295
|
+
return {
|
|
296
|
+
info: err,
|
|
297
|
+
success: (m) => err(`${c.green("\u2713")} ${m}`),
|
|
298
|
+
warn: (m) => err(`${c.yellow("!")} ${m}`),
|
|
299
|
+
error: (m) => err(`${c.red("\u2717")} ${m}`),
|
|
300
|
+
step: err,
|
|
301
|
+
dim: (m) => err(c.dim(m)),
|
|
302
|
+
plain: err
|
|
303
|
+
};
|
|
304
|
+
})();
|
|
12
305
|
var CLI_VERSION_HEADER = "x-genex-cli-version";
|
|
13
306
|
var WORKSPACE_HEADER = "x-genex-workspace";
|
|
14
307
|
function workspaceHeaderValue(label) {
|
|
@@ -54,13 +347,41 @@ var structuredPrinted = /* @__PURE__ */ new WeakSet();
|
|
|
54
347
|
function printedStructuredError(res) {
|
|
55
348
|
return structuredPrinted.has(res);
|
|
56
349
|
}
|
|
57
|
-
|
|
350
|
+
var termsWaitOverride = null;
|
|
351
|
+
function replayable(body) {
|
|
352
|
+
if (body === void 0 || body === null) return true;
|
|
353
|
+
if (typeof body === "string") return true;
|
|
354
|
+
if (body instanceof Uint8Array || body instanceof ArrayBuffer) return true;
|
|
355
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) return true;
|
|
356
|
+
if (body instanceof URLSearchParams) return true;
|
|
357
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return true;
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
async function apiFetch(url, init = {}, opts = {}) {
|
|
58
361
|
const headers = new Headers(init.headers);
|
|
59
362
|
if (!headers.has(CLI_VERSION_HEADER)) headers.set(CLI_VERSION_HEADER, getCliVersion());
|
|
60
363
|
if (workspaceLabel && !headers.has(WORKSPACE_HEADER)) {
|
|
61
364
|
headers.set(WORKSPACE_HEADER, workspaceLabel);
|
|
62
365
|
}
|
|
63
366
|
const res = await fetch(url, { ...init, headers });
|
|
367
|
+
if (res.status === 403 && !opts.noTermsWait && await isTermsRefusal(res)) {
|
|
368
|
+
const log = stderrLogger;
|
|
369
|
+
reportTermsRefusal(log, await termsRefusalUrl(res));
|
|
370
|
+
const auth = headers.get("authorization") ?? headers.get("Authorization");
|
|
371
|
+
const token = auth?.replace(/^Bearer\s+/i, "").trim();
|
|
372
|
+
let accepted = false;
|
|
373
|
+
if (token && replayable(init.body)) {
|
|
374
|
+
accepted = await waitForAcceptance({
|
|
375
|
+
apiUrl: new URL(url).origin,
|
|
376
|
+
token,
|
|
377
|
+
log,
|
|
378
|
+
...termsWaitOverride ?? {}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
if (accepted) return apiFetch(url, init, { ...opts, noTermsWait: true });
|
|
382
|
+
structuredPrinted.add(res);
|
|
383
|
+
return res;
|
|
384
|
+
}
|
|
64
385
|
if (res.status === 426) {
|
|
65
386
|
try {
|
|
66
387
|
const body = await res.clone().json();
|
|
@@ -119,8 +440,8 @@ async function fetchSignedInEmail(apiUrl, token) {
|
|
|
119
440
|
}
|
|
120
441
|
|
|
121
442
|
// src/lib/blender-client.ts
|
|
122
|
-
import
|
|
123
|
-
import
|
|
443
|
+
import fs2 from "fs";
|
|
444
|
+
import path2 from "path";
|
|
124
445
|
var BLENDER_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
125
446
|
var RENDER_MODES = ["solid", "wireframe", "normals", "lit"];
|
|
126
447
|
function isRenderMode(v) {
|
|
@@ -153,19 +474,19 @@ function sheetOf(r) {
|
|
|
153
474
|
function sheetExt(mime) {
|
|
154
475
|
return mime === "image/webp" ? "webp" : "png";
|
|
155
476
|
}
|
|
156
|
-
var SEAT_FILE =
|
|
477
|
+
var SEAT_FILE = path2.join(".genex", "blender-seat.json");
|
|
157
478
|
function readSeatGrant(cwd = process.cwd()) {
|
|
158
479
|
try {
|
|
159
|
-
const raw = JSON.parse(
|
|
480
|
+
const raw = JSON.parse(fs2.readFileSync(path2.join(cwd, SEAT_FILE), "utf8"));
|
|
160
481
|
return typeof raw.url === "string" && typeof raw.token === "string" ? { url: raw.url, token: raw.token } : null;
|
|
161
482
|
} catch {
|
|
162
483
|
return null;
|
|
163
484
|
}
|
|
164
485
|
}
|
|
165
486
|
function writeSeatGrant(grant, cwd = process.cwd()) {
|
|
166
|
-
const file =
|
|
167
|
-
|
|
168
|
-
|
|
487
|
+
const file = path2.join(cwd, SEAT_FILE);
|
|
488
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
489
|
+
fs2.writeFileSync(file, JSON.stringify(grant, null, 2) + "\n", { mode: 384 });
|
|
169
490
|
}
|
|
170
491
|
async function blenderCall(base, route, body) {
|
|
171
492
|
const seat = readSeatGrant();
|
|
@@ -230,18 +551,18 @@ function sceneSummary(s) {
|
|
|
230
551
|
}
|
|
231
552
|
|
|
232
553
|
// src/lib/store.ts
|
|
233
|
-
import
|
|
234
|
-
import
|
|
554
|
+
import fs4 from "fs/promises";
|
|
555
|
+
import path4 from "path";
|
|
235
556
|
|
|
236
557
|
// src/lib/env.ts
|
|
237
|
-
import
|
|
238
|
-
import
|
|
239
|
-
import { spawn } from "child_process";
|
|
558
|
+
import fs3 from "fs/promises";
|
|
559
|
+
import path3 from "path";
|
|
560
|
+
import { spawn as spawn2 } from "child_process";
|
|
240
561
|
async function writeEnvVar(envPath, key, value) {
|
|
241
562
|
let content = "";
|
|
242
563
|
let existed = false;
|
|
243
564
|
try {
|
|
244
|
-
content = await
|
|
565
|
+
content = await fs3.readFile(envPath, "utf8");
|
|
245
566
|
existed = true;
|
|
246
567
|
} catch {
|
|
247
568
|
}
|
|
@@ -261,14 +582,14 @@ async function writeEnvVar(envPath, key, value) {
|
|
|
261
582
|
next = prefix + assignment + "\n";
|
|
262
583
|
mode = existed ? "appended" : "created";
|
|
263
584
|
}
|
|
264
|
-
await
|
|
265
|
-
await
|
|
585
|
+
await fs3.mkdir(path3.dirname(envPath), { recursive: true });
|
|
586
|
+
await fs3.writeFile(envPath, next, { mode: 384 });
|
|
266
587
|
await restrictFilePermissions(envPath);
|
|
267
588
|
return { mode, path: envPath };
|
|
268
589
|
}
|
|
269
590
|
async function restrictFilePermissions(filePath) {
|
|
270
591
|
if (process.platform !== "win32") {
|
|
271
|
-
await
|
|
592
|
+
await fs3.chmod(filePath, 384).catch(() => {
|
|
272
593
|
});
|
|
273
594
|
return;
|
|
274
595
|
}
|
|
@@ -276,7 +597,7 @@ async function restrictFilePermissions(filePath) {
|
|
|
276
597
|
if (!user) return;
|
|
277
598
|
await new Promise((resolve) => {
|
|
278
599
|
try {
|
|
279
|
-
const child =
|
|
600
|
+
const child = spawn2(
|
|
280
601
|
"icacls",
|
|
281
602
|
[filePath, "/inheritance:r", "/grant:r", `${user}:F`],
|
|
282
603
|
{ stdio: "ignore" }
|
|
@@ -300,14 +621,14 @@ function escapeRegExp(s) {
|
|
|
300
621
|
|
|
301
622
|
// src/lib/store.ts
|
|
302
623
|
function getProjectMetadataPath(cwd = process.cwd()) {
|
|
303
|
-
return
|
|
624
|
+
return path4.join(cwd, ".genex", "project.json");
|
|
304
625
|
}
|
|
305
626
|
function getWorkspacePath(cwd = process.cwd()) {
|
|
306
|
-
return
|
|
627
|
+
return path4.join(cwd, ".genex", "workspace.json");
|
|
307
628
|
}
|
|
308
629
|
async function readWorkspace(cwd = process.cwd()) {
|
|
309
630
|
try {
|
|
310
|
-
const raw = await
|
|
631
|
+
const raw = await fs4.readFile(getWorkspacePath(cwd), "utf8");
|
|
311
632
|
return JSON.parse(raw);
|
|
312
633
|
} catch {
|
|
313
634
|
return null;
|
|
@@ -315,9 +636,9 @@ async function readWorkspace(cwd = process.cwd()) {
|
|
|
315
636
|
}
|
|
316
637
|
async function writeWorkspace(meta, cwd = process.cwd()) {
|
|
317
638
|
const file = getWorkspacePath(cwd);
|
|
318
|
-
await
|
|
319
|
-
await
|
|
320
|
-
await
|
|
639
|
+
await fs4.mkdir(path4.dirname(file), { recursive: true });
|
|
640
|
+
await fs4.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
641
|
+
await fs4.chmod(file, 384).catch(() => {
|
|
321
642
|
});
|
|
322
643
|
return { path: file };
|
|
323
644
|
}
|
|
@@ -330,7 +651,7 @@ async function rotateRejectedEnv(envPath) {
|
|
|
330
651
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
331
652
|
const aside = `${file}.rejected-${stamp}`;
|
|
332
653
|
try {
|
|
333
|
-
await
|
|
654
|
+
await fs4.rename(file, aside);
|
|
334
655
|
return aside;
|
|
335
656
|
} catch {
|
|
336
657
|
return null;
|
|
@@ -340,14 +661,14 @@ async function readUserToken(envPath) {
|
|
|
340
661
|
const fromGenex = await readTokenFromFile(getGenexEnvPath(envPath));
|
|
341
662
|
if (fromGenex) return fromGenex;
|
|
342
663
|
if (!envPath && !process.env[ENV_FILE_ENV]) {
|
|
343
|
-
return readTokenFromFile(
|
|
664
|
+
return readTokenFromFile(path4.join(process.cwd(), ".env"));
|
|
344
665
|
}
|
|
345
666
|
return null;
|
|
346
667
|
}
|
|
347
668
|
async function readTokenFromFile(file) {
|
|
348
669
|
let content;
|
|
349
670
|
try {
|
|
350
|
-
content = await
|
|
671
|
+
content = await fs4.readFile(file, "utf8");
|
|
351
672
|
} catch {
|
|
352
673
|
return null;
|
|
353
674
|
}
|
|
@@ -363,7 +684,7 @@ function stripQuotes(v) {
|
|
|
363
684
|
}
|
|
364
685
|
async function readProject(cwd = process.cwd()) {
|
|
365
686
|
try {
|
|
366
|
-
const raw = await
|
|
687
|
+
const raw = await fs4.readFile(getProjectMetadataPath(cwd), "utf8");
|
|
367
688
|
return JSON.parse(raw);
|
|
368
689
|
} catch {
|
|
369
690
|
return null;
|
|
@@ -371,9 +692,9 @@ async function readProject(cwd = process.cwd()) {
|
|
|
371
692
|
}
|
|
372
693
|
async function writeProject(meta, cwd = process.cwd()) {
|
|
373
694
|
const file = getProjectMetadataPath(cwd);
|
|
374
|
-
await
|
|
375
|
-
await
|
|
376
|
-
await
|
|
695
|
+
await fs4.mkdir(path4.dirname(file), { recursive: true });
|
|
696
|
+
await fs4.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
697
|
+
await fs4.chmod(file, 384).catch(() => {
|
|
377
698
|
});
|
|
378
699
|
return { path: file };
|
|
379
700
|
}
|
|
@@ -429,6 +750,21 @@ async function acquireSeat(opts) {
|
|
|
429
750
|
}
|
|
430
751
|
|
|
431
752
|
export {
|
|
753
|
+
run,
|
|
754
|
+
readRemoteSource,
|
|
755
|
+
isStale,
|
|
756
|
+
inHostedSession,
|
|
757
|
+
mayForceOverAnotherDevice,
|
|
758
|
+
reportForceRefused,
|
|
759
|
+
reportStale,
|
|
760
|
+
sourceTreeHash,
|
|
761
|
+
urlHasEmbeddedCredentials,
|
|
762
|
+
fetchCloneGrant,
|
|
763
|
+
cloneSource,
|
|
764
|
+
isTermsRefusal,
|
|
765
|
+
acceptUrl,
|
|
766
|
+
reportTermsRefusal,
|
|
767
|
+
runAccept,
|
|
432
768
|
setWorkspaceHeader,
|
|
433
769
|
printedStructuredError,
|
|
434
770
|
apiFetch,
|