@instafy/cli 0.1.8 → 0.1.9
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 +16 -9
- package/dist/api.js +9 -0
- package/dist/auth.js +392 -23
- package/dist/config.js +150 -6
- package/dist/errors.js +63 -0
- package/dist/git-credential.js +205 -0
- package/dist/git-setup.js +56 -0
- package/dist/git-wrapper.js +502 -0
- package/dist/git.js +11 -5
- package/dist/index.js +293 -108
- package/dist/org.js +9 -4
- package/dist/project-manifest.js +24 -0
- package/dist/project.js +254 -29
- package/dist/rathole.js +14 -10
- package/dist/runtime.js +11 -24
- package/dist/tunnel.js +272 -7
- package/package.json +3 -1
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
const INSTAFY_GIT_HELPER_VALUE = "!instafy git credential";
|
|
7
|
+
function pathExists(candidate) {
|
|
8
|
+
try {
|
|
9
|
+
fs.statSync(candidate);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function findInstafyGitContext(startDir) {
|
|
17
|
+
let current = path.resolve(startDir);
|
|
18
|
+
const root = path.parse(current).root;
|
|
19
|
+
while (true) {
|
|
20
|
+
const gitDir = path.join(current, ".instafy", ".git");
|
|
21
|
+
if (pathExists(gitDir)) {
|
|
22
|
+
return { workTree: current, gitDir };
|
|
23
|
+
}
|
|
24
|
+
if (current === root)
|
|
25
|
+
break;
|
|
26
|
+
current = path.dirname(current);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function splitGitCommand(userArgs) {
|
|
31
|
+
const globalArgs = [];
|
|
32
|
+
let index = 0;
|
|
33
|
+
while (index < userArgs.length) {
|
|
34
|
+
const arg = userArgs[index] ?? "";
|
|
35
|
+
if (arg === "--") {
|
|
36
|
+
index += 1;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
globalArgs.push(arg);
|
|
43
|
+
// Skip values for global options that consume the next token.
|
|
44
|
+
if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
|
|
45
|
+
if (index + 1 < userArgs.length) {
|
|
46
|
+
globalArgs.push(userArgs[index + 1] ?? "");
|
|
47
|
+
index += 2;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
index += 1;
|
|
52
|
+
}
|
|
53
|
+
const command = index < userArgs.length ? (userArgs[index] ?? null) : null;
|
|
54
|
+
const commandArgs = command ? userArgs.slice(index + 1) : [];
|
|
55
|
+
return { globalArgs, command, commandArgs };
|
|
56
|
+
}
|
|
57
|
+
function validateGitArgs(userArgs) {
|
|
58
|
+
let parsingGlobalOptions = true;
|
|
59
|
+
for (let index = 0; index < userArgs.length; index += 1) {
|
|
60
|
+
const arg = userArgs[index] ?? "";
|
|
61
|
+
if (!parsingGlobalOptions) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (arg === "--") {
|
|
65
|
+
parsingGlobalOptions = false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
69
|
+
parsingGlobalOptions = false;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (arg === "--git-dir" || arg.startsWith("--git-dir=") || arg === "--work-tree" || arg.startsWith("--work-tree=") || arg === "-C") {
|
|
73
|
+
throw new Error(`Unsupported argument "${arg}". Use ${kleur.cyan("cd")} instead; ${kleur.cyan("instafy git")} manages --git-dir/--work-tree automatically.`);
|
|
74
|
+
}
|
|
75
|
+
// Skip the value for global options that consume the next token.
|
|
76
|
+
if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
|
|
77
|
+
index += 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function buildInstafyGitArgs(context, userArgs) {
|
|
82
|
+
validateGitArgs(userArgs);
|
|
83
|
+
return ["--git-dir", context.gitDir, "--work-tree", context.workTree, ...userArgs];
|
|
84
|
+
}
|
|
85
|
+
function normalizePathSpec(raw) {
|
|
86
|
+
return (raw ?? "").replace(/\\/g, "/").trim().replace(/^\/+/, "");
|
|
87
|
+
}
|
|
88
|
+
function isReservedSyncPath(pathSpec) {
|
|
89
|
+
const normalized = normalizePathSpec(pathSpec);
|
|
90
|
+
if (!normalized) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (normalized === ".instafy" || normalized.startsWith(".instafy/")) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (normalized === ".instafy/origin-staging" || normalized.startsWith(".instafy/origin-staging/")) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return normalized.split("/").some((segment) => segment === ".git" || segment.startsWith(".git.instafy-hidden-"));
|
|
100
|
+
}
|
|
101
|
+
function gitCapture(context, args, options) {
|
|
102
|
+
const result = spawnSync("git", ["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], {
|
|
103
|
+
cwd: options.cwd,
|
|
104
|
+
encoding: "utf8",
|
|
105
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
status: typeof result.status === "number" ? result.status : 1,
|
|
109
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
110
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
111
|
+
error: result.error,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function collectDirtyPaths(context, options) {
|
|
115
|
+
const result = gitCapture(context, ["status", "--porcelain=v1"], options);
|
|
116
|
+
if (result.error) {
|
|
117
|
+
throw new Error(`Failed to run git status: ${result.error.message}`);
|
|
118
|
+
}
|
|
119
|
+
if (result.status !== 0) {
|
|
120
|
+
throw new Error(`git status failed (${result.status}): ${result.stderr.trim()}`);
|
|
121
|
+
}
|
|
122
|
+
const paths = [];
|
|
123
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
124
|
+
if (!line.trim())
|
|
125
|
+
continue;
|
|
126
|
+
if (line.length < 4)
|
|
127
|
+
continue;
|
|
128
|
+
const raw = line.slice(3).trim();
|
|
129
|
+
if (!raw)
|
|
130
|
+
continue;
|
|
131
|
+
const rename = raw.split(" -> ");
|
|
132
|
+
if (rename.length === 2) {
|
|
133
|
+
for (const entry of rename) {
|
|
134
|
+
const normalized = normalizePathSpec(entry).replace(/\/+$/, "");
|
|
135
|
+
if (normalized)
|
|
136
|
+
paths.push(normalized);
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const normalized = normalizePathSpec(raw).replace(/\/+$/, "");
|
|
141
|
+
if (normalized)
|
|
142
|
+
paths.push(normalized);
|
|
143
|
+
}
|
|
144
|
+
paths.sort((a, b) => a.localeCompare(b));
|
|
145
|
+
const unique = [];
|
|
146
|
+
for (const value of paths) {
|
|
147
|
+
if (unique.length === 0 || unique[unique.length - 1] !== value) {
|
|
148
|
+
unique.push(value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return unique;
|
|
152
|
+
}
|
|
153
|
+
function findEmbeddedGitDirs(context, pathSpecs) {
|
|
154
|
+
const instafyGitDir = path.resolve(context.gitDir);
|
|
155
|
+
const seen = new Set();
|
|
156
|
+
for (const rawPath of pathSpecs) {
|
|
157
|
+
const normalized = normalizePathSpec(rawPath).replace(/\/+$/, "");
|
|
158
|
+
if (!normalized || normalized === "." || normalized === "/") {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
let cursor = normalized;
|
|
162
|
+
while (cursor && cursor !== ".") {
|
|
163
|
+
const candidate = path.join(context.workTree, ...cursor.split("/"), ".git");
|
|
164
|
+
if (pathExists(candidate) && path.resolve(candidate) !== instafyGitDir) {
|
|
165
|
+
seen.add(path.resolve(candidate));
|
|
166
|
+
}
|
|
167
|
+
const next = path.posix.dirname(cursor);
|
|
168
|
+
if (next === "." || next === cursor) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
cursor = next;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const candidates = Array.from(seen);
|
|
175
|
+
candidates.sort((a, b) => {
|
|
176
|
+
const depthA = a.split(path.sep).length;
|
|
177
|
+
const depthB = b.split(path.sep).length;
|
|
178
|
+
return depthB - depthA;
|
|
179
|
+
});
|
|
180
|
+
return candidates;
|
|
181
|
+
}
|
|
182
|
+
function hideEmbeddedGitDirs(context, pathSpecs) {
|
|
183
|
+
const candidates = findEmbeddedGitDirs(context, pathSpecs);
|
|
184
|
+
if (candidates.length === 0) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
const stagingBase = path.join(context.workTree, ".instafy", "origin-staging", "embedded-git");
|
|
188
|
+
fs.mkdirSync(stagingBase, { recursive: true });
|
|
189
|
+
const renames = [];
|
|
190
|
+
for (const candidate of candidates) {
|
|
191
|
+
let hidden = path.join(stagingBase, randomUUID());
|
|
192
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
193
|
+
if (!pathExists(hidden))
|
|
194
|
+
break;
|
|
195
|
+
hidden = path.join(stagingBase, randomUUID());
|
|
196
|
+
}
|
|
197
|
+
fs.renameSync(candidate, hidden);
|
|
198
|
+
renames.push({ original: candidate, hidden });
|
|
199
|
+
}
|
|
200
|
+
return renames;
|
|
201
|
+
}
|
|
202
|
+
function restoreEmbeddedGitDirs(renames) {
|
|
203
|
+
for (let index = renames.length - 1; index >= 0; index -= 1) {
|
|
204
|
+
const rename = renames[index];
|
|
205
|
+
if (!rename)
|
|
206
|
+
continue;
|
|
207
|
+
try {
|
|
208
|
+
fs.renameSync(rename.hidden, rename.original);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// ignore restore failures; leave evidence in .instafy/origin-staging for debugging
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function parseAddArgs(commandArgs) {
|
|
216
|
+
const addOptions = [];
|
|
217
|
+
const pathSpecs = [];
|
|
218
|
+
let inPaths = false;
|
|
219
|
+
for (const arg of commandArgs) {
|
|
220
|
+
if (inPaths) {
|
|
221
|
+
pathSpecs.push(arg);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (arg === "--") {
|
|
225
|
+
inPaths = true;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (arg.startsWith("-") && arg !== "-") {
|
|
229
|
+
addOptions.push(arg);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
inPaths = true;
|
|
233
|
+
pathSpecs.push(arg);
|
|
234
|
+
}
|
|
235
|
+
return { addOptions, pathSpecs };
|
|
236
|
+
}
|
|
237
|
+
function runInstafyGitAdd(context, globalArgs, commandArgs, options) {
|
|
238
|
+
const parsed = parseAddArgs(commandArgs);
|
|
239
|
+
const normalizedPathSpecs = parsed.pathSpecs
|
|
240
|
+
.map((spec) => normalizePathSpec(spec))
|
|
241
|
+
.filter((spec) => spec && spec !== "." && spec !== "/");
|
|
242
|
+
const stageAll = normalizedPathSpecs.length === 0;
|
|
243
|
+
const stagePaths = stageAll
|
|
244
|
+
? collectDirtyPaths(context, options).filter((entry) => !isReservedSyncPath(entry))
|
|
245
|
+
: normalizedPathSpecs.filter((entry) => !isReservedSyncPath(entry));
|
|
246
|
+
if (stagePaths.length === 0) {
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
const addOptions = [...parsed.addOptions];
|
|
250
|
+
if (stageAll && !addOptions.some((opt) => opt === "-A" || opt === "--all")) {
|
|
251
|
+
addOptions.unshift("-A");
|
|
252
|
+
}
|
|
253
|
+
const renames = hideEmbeddedGitDirs(context, stagePaths);
|
|
254
|
+
try {
|
|
255
|
+
const result = spawnSync("git", [
|
|
256
|
+
"--git-dir",
|
|
257
|
+
context.gitDir,
|
|
258
|
+
"--work-tree",
|
|
259
|
+
context.workTree,
|
|
260
|
+
...globalArgs,
|
|
261
|
+
"add",
|
|
262
|
+
...addOptions,
|
|
263
|
+
"--",
|
|
264
|
+
...stagePaths,
|
|
265
|
+
], { stdio: "inherit", cwd: options.cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
|
|
266
|
+
if (result.error) {
|
|
267
|
+
throw new Error(`Failed to run git add: ${result.error.message}`);
|
|
268
|
+
}
|
|
269
|
+
return typeof result.status === "number" ? result.status : 1;
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
restoreEmbeddedGitDirs(renames);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function runInstafyGit(userArgs, options) {
|
|
276
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
277
|
+
if (userArgs.length === 0 || userArgs[0] === "--help" || userArgs[0] === "-h") {
|
|
278
|
+
// Keep this minimal so it doesn't diverge from Git help output.
|
|
279
|
+
console.log("instafy git <git-args...>");
|
|
280
|
+
console.log("");
|
|
281
|
+
console.log(`Runs ${kleur.cyan("git")} against the Instafy canonical repo at ${kleur.cyan(".instafy/.git")} (auto-detected by walking up from cwd).`);
|
|
282
|
+
console.log("");
|
|
283
|
+
console.log("Example:");
|
|
284
|
+
console.log(` ${kleur.cyan('instafy git status')}`);
|
|
285
|
+
console.log(` ${kleur.cyan('instafy git add -A')}`);
|
|
286
|
+
console.log(` ${kleur.cyan('instafy git commit -am "instafy: checkpoint"')}`);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
const context = findInstafyGitContext(cwd);
|
|
290
|
+
if (!context) {
|
|
291
|
+
throw new Error([
|
|
292
|
+
"No Instafy canonical git checkout found (expected .instafy/.git).",
|
|
293
|
+
"",
|
|
294
|
+
"Tips:",
|
|
295
|
+
`- Run this inside a git-canonical workspace (where ${kleur.cyan(".instafy/.git")} exists).`,
|
|
296
|
+
`- If you're trying to operate on a user repo, use normal ${kleur.cyan("git")} (it uses .git).`,
|
|
297
|
+
].join("\n"));
|
|
298
|
+
}
|
|
299
|
+
validateGitArgs(userArgs);
|
|
300
|
+
const split = splitGitCommand(userArgs);
|
|
301
|
+
if (split.command === "add") {
|
|
302
|
+
return runInstafyGitAdd(context, split.globalArgs, split.commandArgs, { cwd });
|
|
303
|
+
}
|
|
304
|
+
const args = buildInstafyGitArgs(context, ["-c", `credential.helper=${INSTAFY_GIT_HELPER_VALUE}`, ...userArgs]);
|
|
305
|
+
const result = spawnSync("git", args, {
|
|
306
|
+
stdio: "inherit",
|
|
307
|
+
cwd,
|
|
308
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
309
|
+
});
|
|
310
|
+
if (result.error) {
|
|
311
|
+
throw new Error(`Failed to run git: ${result.error.message}`);
|
|
312
|
+
}
|
|
313
|
+
return typeof result.status === "number" ? result.status : 1;
|
|
314
|
+
}
|
|
315
|
+
function printGitSyncHelp() {
|
|
316
|
+
console.log("instafy git sync [options]");
|
|
317
|
+
console.log("");
|
|
318
|
+
console.log("Calls the Origin git sync endpoint (POST /git/sync) for the current runtime/workspace.");
|
|
319
|
+
console.log("");
|
|
320
|
+
console.log("Options:");
|
|
321
|
+
console.log(" -m, --message <msg> Commit message (recommended)");
|
|
322
|
+
console.log(" --path <relativePath> Only sync selected path(s) (repeatable)");
|
|
323
|
+
console.log(" --origin-endpoint <url> Override Origin base URL (default: $ORIGIN_ENDPOINT or localhost)");
|
|
324
|
+
console.log(" --origin-token <token> Override Origin bearer token (default: $ORIGIN_INTERNAL_TOKEN)");
|
|
325
|
+
console.log(" --json Output JSON");
|
|
326
|
+
console.log("");
|
|
327
|
+
console.log("Env fallback:");
|
|
328
|
+
console.log(" ORIGIN_ENDPOINT, ORIGIN_BIND_PORT, ORIGIN_INTERNAL_TOKEN, RUNTIME_ACCESS_TOKEN");
|
|
329
|
+
}
|
|
330
|
+
function normalizeToken(raw) {
|
|
331
|
+
if (typeof raw !== "string")
|
|
332
|
+
return null;
|
|
333
|
+
const trimmed = raw.trim();
|
|
334
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
335
|
+
}
|
|
336
|
+
function normalizeUrl(raw) {
|
|
337
|
+
return raw.trim().replace(/\/+$/, "");
|
|
338
|
+
}
|
|
339
|
+
function resolveOriginEndpoint(override) {
|
|
340
|
+
const direct = normalizeToken(override) ?? normalizeToken(process.env["ORIGIN_ENDPOINT"]);
|
|
341
|
+
if (direct) {
|
|
342
|
+
return normalizeUrl(direct);
|
|
343
|
+
}
|
|
344
|
+
const portRaw = normalizeToken(process.env["ORIGIN_BIND_PORT"]);
|
|
345
|
+
const port = portRaw ? Number.parseInt(portRaw, 10) : 54332;
|
|
346
|
+
const resolvedPort = Number.isFinite(port) && port > 0 ? port : 54332;
|
|
347
|
+
return `http://127.0.0.1:${resolvedPort}`;
|
|
348
|
+
}
|
|
349
|
+
function resolveOriginToken(override) {
|
|
350
|
+
const token = normalizeToken(override) ??
|
|
351
|
+
normalizeToken(process.env["ORIGIN_INTERNAL_TOKEN"]) ??
|
|
352
|
+
normalizeToken(process.env["RUNTIME_ACCESS_TOKEN"]);
|
|
353
|
+
if (!token) {
|
|
354
|
+
throw new Error([
|
|
355
|
+
"Origin bearer token missing.",
|
|
356
|
+
"",
|
|
357
|
+
"Provide one of:",
|
|
358
|
+
"- --origin-token <token>",
|
|
359
|
+
"- ORIGIN_INTERNAL_TOKEN=<token> (recommended inside runtimes)",
|
|
360
|
+
"- RUNTIME_ACCESS_TOKEN=<token>",
|
|
361
|
+
].join("\n"));
|
|
362
|
+
}
|
|
363
|
+
return token;
|
|
364
|
+
}
|
|
365
|
+
function parseGitSyncArgs(args) {
|
|
366
|
+
let message = null;
|
|
367
|
+
let json = false;
|
|
368
|
+
let originEndpointOverride = null;
|
|
369
|
+
let originTokenOverride = null;
|
|
370
|
+
const paths = [];
|
|
371
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
372
|
+
const arg = args[index] ?? "";
|
|
373
|
+
if (arg === "--help" || arg === "-h") {
|
|
374
|
+
return "help";
|
|
375
|
+
}
|
|
376
|
+
if (arg === "--json") {
|
|
377
|
+
json = true;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (arg === "-m" || arg === "--message") {
|
|
381
|
+
const value = args[index + 1];
|
|
382
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
383
|
+
throw new Error(`${arg} requires a non-empty value`);
|
|
384
|
+
}
|
|
385
|
+
message = value.trim();
|
|
386
|
+
index += 1;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (arg.startsWith("--message=")) {
|
|
390
|
+
const value = arg.slice("--message=".length).trim();
|
|
391
|
+
if (!value) {
|
|
392
|
+
throw new Error("--message requires a non-empty value");
|
|
393
|
+
}
|
|
394
|
+
message = value;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (arg === "--origin-endpoint") {
|
|
398
|
+
const value = args[index + 1];
|
|
399
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
400
|
+
throw new Error("--origin-endpoint requires a URL");
|
|
401
|
+
}
|
|
402
|
+
originEndpointOverride = value.trim();
|
|
403
|
+
index += 1;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (arg.startsWith("--origin-endpoint=")) {
|
|
407
|
+
const value = arg.slice("--origin-endpoint=".length).trim();
|
|
408
|
+
if (!value) {
|
|
409
|
+
throw new Error("--origin-endpoint requires a URL");
|
|
410
|
+
}
|
|
411
|
+
originEndpointOverride = value;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (arg === "--origin-token") {
|
|
415
|
+
const value = args[index + 1];
|
|
416
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
417
|
+
throw new Error("--origin-token requires a token");
|
|
418
|
+
}
|
|
419
|
+
originTokenOverride = value.trim();
|
|
420
|
+
index += 1;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (arg.startsWith("--origin-token=")) {
|
|
424
|
+
const value = arg.slice("--origin-token=".length).trim();
|
|
425
|
+
if (!value) {
|
|
426
|
+
throw new Error("--origin-token requires a token");
|
|
427
|
+
}
|
|
428
|
+
originTokenOverride = value;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (arg === "--path") {
|
|
432
|
+
const value = args[index + 1];
|
|
433
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
434
|
+
throw new Error("--path requires a relative path");
|
|
435
|
+
}
|
|
436
|
+
paths.push(value.trim());
|
|
437
|
+
index += 1;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (arg.startsWith("--path=")) {
|
|
441
|
+
const value = arg.slice("--path=".length).trim();
|
|
442
|
+
if (!value) {
|
|
443
|
+
throw new Error("--path requires a relative path");
|
|
444
|
+
}
|
|
445
|
+
paths.push(value);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
449
|
+
}
|
|
450
|
+
const originEndpoint = resolveOriginEndpoint(originEndpointOverride);
|
|
451
|
+
const originToken = resolveOriginToken(originTokenOverride);
|
|
452
|
+
const resolvedMessage = message ?? `instafy: sync ${new Date().toISOString().replace(/\..*$/, "")}Z`;
|
|
453
|
+
return {
|
|
454
|
+
message: resolvedMessage,
|
|
455
|
+
json,
|
|
456
|
+
originEndpoint,
|
|
457
|
+
originToken,
|
|
458
|
+
paths: paths.length > 0 ? paths : null,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
export async function runInstafyGitSync(args, options) {
|
|
462
|
+
void options;
|
|
463
|
+
const parsed = parseGitSyncArgs(args);
|
|
464
|
+
if (parsed === "help") {
|
|
465
|
+
printGitSyncHelp();
|
|
466
|
+
return 0;
|
|
467
|
+
}
|
|
468
|
+
const url = `${parsed.originEndpoint.replace(/\/+$/, "")}/git/sync`;
|
|
469
|
+
const response = await fetch(url, {
|
|
470
|
+
method: "POST",
|
|
471
|
+
headers: {
|
|
472
|
+
accept: "application/json",
|
|
473
|
+
authorization: `Bearer ${parsed.originToken}`,
|
|
474
|
+
"content-type": "application/json",
|
|
475
|
+
},
|
|
476
|
+
body: JSON.stringify({
|
|
477
|
+
message: parsed.message,
|
|
478
|
+
paths: parsed.paths ?? undefined,
|
|
479
|
+
}),
|
|
480
|
+
}).catch((error) => {
|
|
481
|
+
throw new Error(`Origin git sync request failed: ${String(error)}`);
|
|
482
|
+
});
|
|
483
|
+
const text = await response.text().catch(() => "");
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
const suffix = text.trim() ? `: ${text.trim()}` : "";
|
|
486
|
+
console.error(kleur.red(`Origin git sync failed (${response.status} ${response.statusText})${suffix}`));
|
|
487
|
+
return 1;
|
|
488
|
+
}
|
|
489
|
+
const payload = text.trim() ? JSON.parse(text) : null;
|
|
490
|
+
const rev = typeof payload?.rev === "string" ? payload.rev.trim() : "";
|
|
491
|
+
if (!rev) {
|
|
492
|
+
console.error(kleur.red("Origin git sync response missing rev field."));
|
|
493
|
+
return 1;
|
|
494
|
+
}
|
|
495
|
+
if (parsed.json) {
|
|
496
|
+
console.log(JSON.stringify({ rev }, null, 2));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
console.log(rev);
|
|
500
|
+
}
|
|
501
|
+
return 0;
|
|
502
|
+
}
|
package/dist/git.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
|
-
import { resolveConfiguredAccessToken } from "./config.js";
|
|
4
|
+
import { resolveConfiguredAccessToken, resolveControllerUrl } from "./config.js";
|
|
5
|
+
import { formatAuthRejectedError } from "./errors.js";
|
|
5
6
|
function normalizeToken(value) {
|
|
6
7
|
if (typeof value !== "string") {
|
|
7
8
|
return null;
|
|
@@ -25,6 +26,7 @@ function resolveControllerAccessToken(options) {
|
|
|
25
26
|
return (normalizeToken(options.controllerAccessToken) ??
|
|
26
27
|
normalizeToken(process.env["INSTAFY_ACCESS_TOKEN"]) ??
|
|
27
28
|
normalizeToken(process.env["CONTROLLER_ACCESS_TOKEN"]) ??
|
|
29
|
+
normalizeToken(process.env["RUNTIME_ACCESS_TOKEN"]) ??
|
|
28
30
|
normalizeToken(options.supabaseAccessToken) ??
|
|
29
31
|
readTokenFromFile(options.supabaseAccessTokenFile) ??
|
|
30
32
|
normalizeToken(process.env["SUPABASE_ACCESS_TOKEN"]) ??
|
|
@@ -43,6 +45,7 @@ export async function mintGitAccessToken(params) {
|
|
|
43
45
|
authorization: `Bearer ${params.controllerAccessToken}`,
|
|
44
46
|
"content-type": "application/json",
|
|
45
47
|
},
|
|
48
|
+
signal: params.signal,
|
|
46
49
|
body: JSON.stringify({
|
|
47
50
|
scopes,
|
|
48
51
|
ttlSeconds: params.ttlSeconds,
|
|
@@ -50,6 +53,12 @@ export async function mintGitAccessToken(params) {
|
|
|
50
53
|
});
|
|
51
54
|
if (!response.ok) {
|
|
52
55
|
const text = await response.text().catch(() => "");
|
|
56
|
+
if (response.status === 401 || response.status === 403) {
|
|
57
|
+
throw formatAuthRejectedError({
|
|
58
|
+
status: response.status,
|
|
59
|
+
responseBody: text,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
53
62
|
throw new Error(`Instafy server rejected git token request (${response.status} ${response.statusText}): ${text}`);
|
|
54
63
|
}
|
|
55
64
|
const payload = (await response.json());
|
|
@@ -66,10 +75,7 @@ export async function mintGitAccessToken(params) {
|
|
|
66
75
|
};
|
|
67
76
|
}
|
|
68
77
|
export async function gitToken(options) {
|
|
69
|
-
const controllerUrl = options.controllerUrl ??
|
|
70
|
-
process.env["INSTAFY_SERVER_URL"] ??
|
|
71
|
-
process.env["CONTROLLER_BASE_URL"] ??
|
|
72
|
-
"http://127.0.0.1:8788";
|
|
78
|
+
const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
|
|
73
79
|
const token = resolveControllerAccessToken({
|
|
74
80
|
controllerAccessToken: options.controllerAccessToken,
|
|
75
81
|
supabaseAccessToken: options.supabaseAccessToken,
|