@fixprompt/cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +258 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -86,12 +86,251 @@ function detectContext() {
|
|
|
86
86
|
return merged;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// src/connect.ts
|
|
90
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
91
|
+
import { join } from "path";
|
|
92
|
+
import { spawnSync } from "child_process";
|
|
93
|
+
var DEFAULT_ENDPOINT = "https://geosloghub-production.up.railway.app";
|
|
94
|
+
var TOKEN_ENV_VAR_NAME = "FIXPROMPT_DEPLOY_TOKEN";
|
|
95
|
+
function fileExists(p) {
|
|
96
|
+
try {
|
|
97
|
+
return statSync(p).isFile();
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function dirExists(p) {
|
|
103
|
+
try {
|
|
104
|
+
return statSync(p).isDirectory();
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function detectProvider(cwd) {
|
|
110
|
+
if (fileExists(join(cwd, "app.json")) || fileExists(join(cwd, "app.config.js")) || fileExists(join(cwd, "app.config.ts")) || fileExists(join(cwd, "eas.json"))) {
|
|
111
|
+
return "eas";
|
|
112
|
+
}
|
|
113
|
+
if (fileExists(join(cwd, "vercel.json")) || fileExists(join(cwd, ".vercel", "project.json"))) {
|
|
114
|
+
return "vercel";
|
|
115
|
+
}
|
|
116
|
+
const wfDir = join(cwd, ".github", "workflows");
|
|
117
|
+
if (dirExists(wfDir)) {
|
|
118
|
+
try {
|
|
119
|
+
const files = readdirSync(wfDir);
|
|
120
|
+
if (files.some((f) => /\.ya?ml$/i.test(f))) return "github-actions";
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
async function mintDeployToken(opts) {
|
|
127
|
+
const url = `${opts.endpoint.replace(/\/$/, "")}/admin/projects/${opts.projectId}/deploy-tokens`;
|
|
128
|
+
const res = await fetch(url, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: {
|
|
131
|
+
"x-internal-admin-token": opts.adminToken,
|
|
132
|
+
"Content-Type": "application/json"
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({ label: opts.label })
|
|
135
|
+
});
|
|
136
|
+
const text = await res.text();
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
throw new Error(`broker /admin/projects/${opts.projectId}/deploy-tokens responded ${res.status}: ${text.slice(0, 200)}`);
|
|
139
|
+
}
|
|
140
|
+
return JSON.parse(text);
|
|
141
|
+
}
|
|
142
|
+
function writeEas(token, env, force) {
|
|
143
|
+
console.log(`
|
|
144
|
+
\u2022 Writing ${TOKEN_ENV_VAR_NAME} to EAS env '${env}'\u2026`);
|
|
145
|
+
const useShell = process.platform === "win32";
|
|
146
|
+
const args = [
|
|
147
|
+
"eas-cli",
|
|
148
|
+
"env:create",
|
|
149
|
+
env,
|
|
150
|
+
"--name",
|
|
151
|
+
TOKEN_ENV_VAR_NAME,
|
|
152
|
+
"--value",
|
|
153
|
+
token,
|
|
154
|
+
"--type",
|
|
155
|
+
"string",
|
|
156
|
+
"--visibility",
|
|
157
|
+
"plaintext",
|
|
158
|
+
"--non-interactive"
|
|
159
|
+
];
|
|
160
|
+
if (force) args.push("--force");
|
|
161
|
+
const r = spawnSync("npx", useShell ? args.map((a) => `"${a.replace(/"/g, '\\"')}"`) : args, {
|
|
162
|
+
stdio: "inherit",
|
|
163
|
+
shell: useShell
|
|
164
|
+
});
|
|
165
|
+
if (r.status !== 0) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`eas env:create exited ${r.status}. Common causes:
|
|
168
|
+
\u2022 Not logged in to EAS \u2014 run 'eas login'
|
|
169
|
+
\u2022 Wrong env name \u2014 pass --eas-env <production|preview|development>
|
|
170
|
+
\u2022 Variable already exists \u2014 re-run with --force to overwrite.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function writeGitHubActions(token, cwd) {
|
|
175
|
+
console.log(`
|
|
176
|
+
\u2022 Writing ${TOKEN_ENV_VAR_NAME} to GitHub Actions repo secrets\u2026`);
|
|
177
|
+
const useShell = process.platform === "win32";
|
|
178
|
+
const args = ["secret", "set", TOKEN_ENV_VAR_NAME, "--body", "-"];
|
|
179
|
+
const r = spawnSync("gh", useShell ? args.map((a) => `"${a.replace(/"/g, '\\"')}"`) : args, {
|
|
180
|
+
input: token,
|
|
181
|
+
cwd,
|
|
182
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
183
|
+
shell: useShell
|
|
184
|
+
});
|
|
185
|
+
if (r.status !== 0) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`gh secret set exited ${r.status}. Common causes:
|
|
188
|
+
\u2022 Not logged in to gh \u2014 run 'gh auth login'
|
|
189
|
+
\u2022 Wrong repo \u2014 run 'fixprompt connect' from the repo root, or set GH_REPO.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function writeVercel(token, target) {
|
|
194
|
+
const vt = process.env.VERCEL_TOKEN;
|
|
195
|
+
if (!vt) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Vercel provider needs VERCEL_TOKEN env. Generate one at
|
|
198
|
+
https://vercel.com/account/tokens
|
|
199
|
+
then re-run with that token exported.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
let projectId = target.vercelProjectId;
|
|
203
|
+
if (!projectId) {
|
|
204
|
+
const pj = join(process.cwd(), ".vercel", "project.json");
|
|
205
|
+
if (!fileExists(pj)) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Could not resolve Vercel project id. Either run from a 'vercel link'-ed
|
|
208
|
+
directory or pass --vercel-project-id <prj_\u2026>.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const parsed = JSON.parse(readFileSync(pj, "utf8"));
|
|
213
|
+
projectId = parsed.projectId;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
throw new Error(`failed to read .vercel/project.json: ${e.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const targets = target.vercelTargets ?? ["production", "preview"];
|
|
219
|
+
console.log(`
|
|
220
|
+
\u2022 Writing ${TOKEN_ENV_VAR_NAME} to Vercel project ${projectId} (${targets.join(", ")})\u2026`);
|
|
221
|
+
const res = await fetch(`https://api.vercel.com/v10/projects/${projectId}/env`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
Authorization: `Bearer ${vt}`,
|
|
225
|
+
"Content-Type": "application/json"
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
key: TOKEN_ENV_VAR_NAME,
|
|
229
|
+
value: token,
|
|
230
|
+
type: "plain",
|
|
231
|
+
target: targets
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
const text = await res.text();
|
|
235
|
+
if (!res.ok) {
|
|
236
|
+
throw new Error(`Vercel /env responded ${res.status}: ${text.slice(0, 200)}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function connect(args) {
|
|
240
|
+
const cwd = process.cwd();
|
|
241
|
+
const adminToken = args.flags["admin-token"] ?? process.env.FIXPROMPT_ADMIN_TOKEN ?? process.env.INTERNAL_ADMIN_TOKEN;
|
|
242
|
+
if (!adminToken) {
|
|
243
|
+
console.error(
|
|
244
|
+
"Missing --admin-token (or $FIXPROMPT_ADMIN_TOKEN / $INTERNAL_ADMIN_TOKEN).\n Per-user device-code auth lands in a later release; for now this is the\n broker-wide admin token (single-tenant). Treat it as a root secret."
|
|
245
|
+
);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
const endpoint = (args.flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
|
|
249
|
+
let projectId = args.flags["project-id"] ?? process.env.FIXPROMPT_PROJECT_ID ?? null;
|
|
250
|
+
const projectSlug = args.flags["project-slug"] ?? null;
|
|
251
|
+
if (!projectId && projectSlug) {
|
|
252
|
+
const res = await fetch(`${endpoint}/admin/projects/by-slug/${encodeURIComponent(projectSlug)}`, {
|
|
253
|
+
headers: { "x-internal-admin-token": adminToken }
|
|
254
|
+
});
|
|
255
|
+
const text = await res.text();
|
|
256
|
+
if (!res.ok) {
|
|
257
|
+
console.error(`Project lookup failed (${res.status}): ${text.slice(0, 200)}`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
const parsed = JSON.parse(text);
|
|
261
|
+
projectId = parsed.id;
|
|
262
|
+
console.log(` resolved --project-slug=${projectSlug} \u2192 ${parsed.id} (${parsed.name})`);
|
|
263
|
+
}
|
|
264
|
+
if (!projectId) {
|
|
265
|
+
console.error(
|
|
266
|
+
"Need --project-id or --project-slug (or $FIXPROMPT_PROJECT_ID).\n --project-slug is easiest: it matches the value you see in the dashboard."
|
|
267
|
+
);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
const explicit = args.flags.provider;
|
|
271
|
+
const provider = explicit ?? detectProvider(cwd);
|
|
272
|
+
if (!provider) {
|
|
273
|
+
console.error(
|
|
274
|
+
`Couldn't detect a provider from ${cwd}. Pass --provider <eas|vercel|github-actions>.
|
|
275
|
+
Detection looks for: app.json/eas.json (EAS), .vercel/project.json (Vercel),
|
|
276
|
+
.github/workflows/*.yml (GitHub Actions).`
|
|
277
|
+
);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
const target = {
|
|
281
|
+
provider,
|
|
282
|
+
easEnv: args.flags["eas-env"] ?? "production",
|
|
283
|
+
vercelProjectId: args.flags["vercel-project-id"] ?? void 0,
|
|
284
|
+
vercelTargets: typeof args.flags["vercel-targets"] === "string" ? args.flags["vercel-targets"].split(",").map((t) => t.trim()) : void 0
|
|
285
|
+
};
|
|
286
|
+
const label = args.flags.label ?? `${provider}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
|
|
287
|
+
console.log(`fixprompt connect`);
|
|
288
|
+
console.log(` provider : ${provider}`);
|
|
289
|
+
console.log(` project_id : ${projectId}`);
|
|
290
|
+
console.log(` endpoint : ${endpoint}`);
|
|
291
|
+
console.log(` label : ${label}`);
|
|
292
|
+
if (provider === "eas") console.log(` eas-env : ${target.easEnv}`);
|
|
293
|
+
console.log(`
|
|
294
|
+
\u2022 Minting deploy token via broker\u2026`);
|
|
295
|
+
const minted = await mintDeployToken({
|
|
296
|
+
endpoint,
|
|
297
|
+
adminToken,
|
|
298
|
+
projectId,
|
|
299
|
+
label
|
|
300
|
+
});
|
|
301
|
+
console.log(` \u2713 token_id ${minted.token_id} (value hidden \u2014 written to CI store only)`);
|
|
302
|
+
const force = args.flags.force === true;
|
|
303
|
+
switch (provider) {
|
|
304
|
+
case "eas":
|
|
305
|
+
writeEas(minted.token, target.easEnv, force);
|
|
306
|
+
break;
|
|
307
|
+
case "github-actions":
|
|
308
|
+
writeGitHubActions(minted.token, cwd);
|
|
309
|
+
break;
|
|
310
|
+
case "vercel":
|
|
311
|
+
await writeVercel(minted.token, target);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
console.log(`
|
|
315
|
+
\u2713 ${TOKEN_ENV_VAR_NAME} is now set on ${provider}.`);
|
|
316
|
+
console.log(` Next deploy can run with no shell paste:`);
|
|
317
|
+
if (provider === "eas") {
|
|
318
|
+
console.log(` npm run release:ota --branch=production --message="..."`);
|
|
319
|
+
} else if (provider === "github-actions") {
|
|
320
|
+
console.log(` Reference \${{ secrets.${TOKEN_ENV_VAR_NAME} }} in your workflow.`);
|
|
321
|
+
} else {
|
|
322
|
+
console.log(` The token is in your Vercel project env for the next deploy.`);
|
|
323
|
+
}
|
|
324
|
+
console.log(`
|
|
325
|
+
To revoke later: dashboard \u2192 Integrations \u2192 Deploy tokens \u2192 ${minted.token_id}.`);
|
|
326
|
+
}
|
|
327
|
+
|
|
89
328
|
// src/version.ts
|
|
90
329
|
var CLI_NAME = "@fixprompt/cli";
|
|
91
|
-
var CLI_VERSION = "0.1
|
|
330
|
+
var CLI_VERSION = "0.2.1";
|
|
92
331
|
|
|
93
332
|
// src/cli.ts
|
|
94
|
-
var
|
|
333
|
+
var DEFAULT_ENDPOINT2 = "https://geosloghub-production.up.railway.app";
|
|
95
334
|
function parseArgs(argv) {
|
|
96
335
|
const flags = {};
|
|
97
336
|
const positional = [];
|
|
@@ -122,9 +361,21 @@ function helpText() {
|
|
|
122
361
|
return `${CLI_NAME} v${CLI_VERSION}
|
|
123
362
|
|
|
124
363
|
Usage:
|
|
364
|
+
fixprompt connect [options]
|
|
125
365
|
fixprompt deploy-start [options]
|
|
126
366
|
fixprompt help
|
|
127
367
|
|
|
368
|
+
connect options:
|
|
369
|
+
--project-id <uuid> FixPrompt project id (or $FIXPROMPT_PROJECT_ID)
|
|
370
|
+
--admin-token <fpa_\u2026> Broker admin token (or $FIXPROMPT_ADMIN_TOKEN)
|
|
371
|
+
--provider <name> eas | vercel | github-actions (auto-detected from cwd)
|
|
372
|
+
--eas-env <env> EAS env to write to (default: production)
|
|
373
|
+
--vercel-project-id <id> Override Vercel project id (default: .vercel/project.json)
|
|
374
|
+
--vercel-targets <list> Comma-separated (default: production,preview)
|
|
375
|
+
--label <text> Token label (default: <provider>-<timestamp>)
|
|
376
|
+
--force Overwrite an existing FIXPROMPT_DEPLOY_TOKEN value
|
|
377
|
+
--project-slug <slug> Look up project_id by slug (no UUID needed)
|
|
378
|
+
|
|
128
379
|
deploy-start auth (pick one):
|
|
129
380
|
--deploy-token <fpd_...> Deploy-only token from dashboard (recommended for CI)
|
|
130
381
|
(or $FIXPROMPT_DEPLOY_TOKEN env)
|
|
@@ -134,7 +385,7 @@ deploy-start auth (pick one):
|
|
|
134
385
|
deploy-start options:
|
|
135
386
|
--source <slug> LOGHUB_SOURCE (required only with --key; optional with --deploy-token)
|
|
136
387
|
(default: $LOGHUB_SOURCE or $FIXPROMPT_SOURCE env)
|
|
137
|
-
--endpoint <url> Broker URL (default: $FIXPROMPT_ENDPOINT or ${
|
|
388
|
+
--endpoint <url> Broker URL (default: $FIXPROMPT_ENDPOINT or ${DEFAULT_ENDPOINT2})
|
|
138
389
|
--status <state> in_progress | success | failed (default: in_progress)
|
|
139
390
|
--commit-sha <sha> Override auto-detected commit
|
|
140
391
|
--branch <name> Override auto-detected branch
|
|
@@ -161,7 +412,7 @@ function readKey(flags) {
|
|
|
161
412
|
return v || null;
|
|
162
413
|
}
|
|
163
414
|
function readEndpoint(flags) {
|
|
164
|
-
const v = flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ??
|
|
415
|
+
const v = flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT2;
|
|
165
416
|
return v.replace(/\/$/, "");
|
|
166
417
|
}
|
|
167
418
|
async function deployStart(args) {
|
|
@@ -231,6 +482,9 @@ async function deployStart(args) {
|
|
|
231
482
|
async function main() {
|
|
232
483
|
const args = parseArgs(process.argv.slice(2));
|
|
233
484
|
switch (args.command) {
|
|
485
|
+
case "connect":
|
|
486
|
+
await connect(args);
|
|
487
|
+
break;
|
|
234
488
|
case "deploy-start":
|
|
235
489
|
await deployStart(args);
|
|
236
490
|
break;
|
package/package.json
CHANGED