youiyoux 0.1.15 → 0.1.16
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/chunks/{chunk-CZJFEND6.js → chunk-3SBSYNAG.js} +1 -1
- package/dist/chunks/{chunk-QEUZDGIF.js → chunk-ED35SP3F.js} +3 -3
- package/dist/chunks/{chunk-FTL53MU3.js → chunk-FO3WR47A.js} +1 -1
- package/dist/chunks/{chunk-T4EHV4XY.js → chunk-WMIJNZIL.js} +55 -6
- package/dist/chunks/{chunk-DAGYR4NV.js → chunk-XSIH7RR4.js} +5 -2
- package/dist/chunks/figma-auth-ODUC3TSR.js +212 -0
- package/dist/chunks/{ingest-R2TM3QBM.js → ingest-R5CX6R7G.js} +5 -5
- package/dist/chunks/{src-IMWXYWQK.js → src-FBTLHBVB.js} +1 -1
- package/dist/chunks/{src-OHHZY3TA.js → src-LAUPNDCB.js} +1 -1
- package/dist/hub-previews.js +3 -3
- package/dist/hub-server.js +2 -2
- package/dist/hub-snapshot.js +1 -1
- package/dist/youi.js +3 -3
- package/dist/youiyoux.js +3 -3
- package/package.json +4 -4
|
@@ -4,7 +4,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
|
|
|
4
4
|
var PACKAGES = {
|
|
5
5
|
core: () => import("./src-4BOFDAZD.js"),
|
|
6
6
|
scanner: () => import("./src-6JO73FOS.js"),
|
|
7
|
-
discover: () => import("./src-
|
|
7
|
+
discover: () => import("./src-FBTLHBVB.js"),
|
|
8
8
|
tokens: () => import("./src-WMGLZKYK.js")
|
|
9
9
|
};
|
|
10
10
|
var loadPackage = (name) => PACKAGES[name]();
|
|
@@ -57,7 +57,7 @@ import { homedir, userInfo } from "node:os";
|
|
|
57
57
|
import { join, resolve } from "node:path";
|
|
58
58
|
async function swapImpact(candidate, family) {
|
|
59
59
|
try {
|
|
60
|
-
const { discoverIndexPath, sharedStore } = await import("./src-
|
|
60
|
+
const { discoverIndexPath, sharedStore } = await import("./src-FBTLHBVB.js");
|
|
61
61
|
const path = discoverIndexPath();
|
|
62
62
|
if (!process.env.DATABASE_URL && !existsSync(path)) return null;
|
|
63
63
|
const impact = await (await sharedStore(path, process.env, { readOnly: true })).swapImpact(candidate, family);
|
|
@@ -68,7 +68,7 @@ async function swapImpact(candidate, family) {
|
|
|
68
68
|
}
|
|
69
69
|
async function libraryLicence(id) {
|
|
70
70
|
try {
|
|
71
|
-
const { discoverCacheDir, findListing } = await import("./src-
|
|
71
|
+
const { discoverCacheDir, findListing } = await import("./src-FBTLHBVB.js");
|
|
72
72
|
const { entry } = await findListing(discoverCacheDir(void 0, process.env), id);
|
|
73
73
|
return entry.id === id ? entry.license : null;
|
|
74
74
|
} catch {
|
|
@@ -79,7 +79,7 @@ function workflowStateDir(project, state) {
|
|
|
79
79
|
return resolve(state ?? join(homedir(), ".youi", "workflow", shortHash(`${project.repo.root}\0${project.root}`)));
|
|
80
80
|
}
|
|
81
81
|
async function ingest(link) {
|
|
82
|
-
const { readLink } = await import("./ingest-
|
|
82
|
+
const { readLink } = await import("./ingest-R5CX6R7G.js");
|
|
83
83
|
return readLink(link, { allowLocal: false });
|
|
84
84
|
}
|
|
85
85
|
async function startRuleEditor(options) {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
startRuleEditor,
|
|
10
10
|
style,
|
|
11
11
|
truncate
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ED35SP3F.js";
|
|
13
13
|
import {
|
|
14
14
|
BASELINE_FILE,
|
|
15
15
|
DEFAULT_EXCLUDE,
|
|
@@ -96,7 +96,7 @@ import {
|
|
|
96
96
|
sharedStore,
|
|
97
97
|
showFromCatalog,
|
|
98
98
|
syncIndex
|
|
99
|
-
} from "./chunk-
|
|
99
|
+
} from "./chunk-XSIH7RR4.js";
|
|
100
100
|
import {
|
|
101
101
|
checkpointSqlite,
|
|
102
102
|
indexHome,
|
|
@@ -182,7 +182,7 @@ import { join as join2, relative, resolve } from "node:path";
|
|
|
182
182
|
|
|
183
183
|
// ../cli/src/command.ts
|
|
184
184
|
var COMMAND = "youiyoux";
|
|
185
|
-
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "upgrade", "record", "hub", "init", "adopt", "status", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
|
|
185
|
+
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "upgrade", "record", "hub", "figma", "init", "adopt", "status", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
|
|
186
186
|
|
|
187
187
|
// ../cli/src/agents.ts
|
|
188
188
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -5598,8 +5598,13 @@ ${style.bold("System")} ${style.dim("approved elements: build only with these")
|
|
|
5598
5598
|
${style.bold("Library")} ${style.dim("ingested references: browse and request adoption, never build with")}
|
|
5599
5599
|
library search <query> Library items (lib: ids) and the System elements each could swap in for
|
|
5600
5600
|
library request <id> --reason <text> draft an adoption request for a person to approve
|
|
5601
|
+
figma login [--paste] [--port n] | logout | status
|
|
5602
|
+
sign in to Figma once, for Figma links: through the browser with your team's
|
|
5603
|
+
private Figma app (FIGMA_CLIENT_ID, FIGMA_CLIENT_SECRET), else with a personal
|
|
5604
|
+
access token you paste; kept in the macOS Keychain (else a file only you can
|
|
5605
|
+
read). FIGMA_TOKEN still wins
|
|
5601
5606
|
library ingest <link> [--draft] [--json]
|
|
5602
|
-
read what a GitHub repository, a Storybook, a Figma file (
|
|
5607
|
+
read what a GitHub repository, a Storybook, a Figma file (youiyoux figma login) or a
|
|
5603
5608
|
DESIGN.md holds into Library items, each sorted into a role, and show them;
|
|
5604
5609
|
--draft files them as one draft in the changes queue, where a person keeps or
|
|
5605
5610
|
drops each and approves the rest (youiyoux ui > Changes). GITHUB_TOKEN raises
|
|
@@ -5720,6 +5725,7 @@ var OPTIONS = {
|
|
|
5720
5725
|
db: { type: "string" },
|
|
5721
5726
|
record: { type: "boolean", default: false },
|
|
5722
5727
|
draft: { type: "boolean", default: false },
|
|
5728
|
+
paste: { type: "boolean", default: false },
|
|
5723
5729
|
cache: { type: "string" },
|
|
5724
5730
|
role: { type: "string" },
|
|
5725
5731
|
license: { type: "string" },
|
|
@@ -6001,7 +6007,7 @@ async function cmdLibrary(values, positionals) {
|
|
|
6001
6007
|
return 0;
|
|
6002
6008
|
}
|
|
6003
6009
|
if (action === "ingest" && query && positionals.length === 2) {
|
|
6004
|
-
const { fileIngestDraft, readLink, renderIngest } = await import("./ingest-
|
|
6010
|
+
const { fileIngestDraft, readLink, renderIngest } = await import("./ingest-R5CX6R7G.js");
|
|
6005
6011
|
const p = values.draft ? await project(values) : null;
|
|
6006
6012
|
const reading = await readLink(query, { allowLocal: true });
|
|
6007
6013
|
const filed = p ? await fileIngestDraft(p, reading, values.state) : null;
|
|
@@ -6040,6 +6046,48 @@ async function cmdRecord(values) {
|
|
|
6040
6046
|
else print([renderRecord(record, sentTo), ...values.out ? [style.dim(` written to ${values.out}`)] : [], ...!values.send && !values.out ? [style.dim(` youiyoux record --send posts it to the hub in ${HUB_URL_ENV}; --out <file> writes it; --json prints it`)] : []].join("\n"));
|
|
6041
6047
|
return 0;
|
|
6042
6048
|
}
|
|
6049
|
+
async function cmdFigma(values, positionals) {
|
|
6050
|
+
const [action, ...extra] = positionals;
|
|
6051
|
+
const auth = await import("./figma-auth-ODUC3TSR.js");
|
|
6052
|
+
const store = auth.credentialStore();
|
|
6053
|
+
if (extra.length > 0 || !["login", "logout", "status"].includes(action ?? "")) throw new UsageError("youiyoux figma login [--paste] [--port n] or youiyoux figma logout or youiyoux figma status");
|
|
6054
|
+
if (action === "status") {
|
|
6055
|
+
const stored = await store.get();
|
|
6056
|
+
const lines = process.env.FIGMA_TOKEN ? [`${style.ok("FIGMA_TOKEN")} is set: it is used, before any sign-in`] : [];
|
|
6057
|
+
if (!stored) lines.push(process.env.FIGMA_TOKEN ? style.dim("no sign-in kept") : `${style.warn("not signed in")} to Figma: youiyoux figma login`);
|
|
6058
|
+
else if (stored.kind === "token") lines.push(`${style.ok("signed in")}${stored.handle ? ` as ${stored.handle}` : ""} with a personal access token, kept in ${store.where} since ${stored.savedAt.slice(0, 10)}`);
|
|
6059
|
+
else lines.push(`${style.ok("signed in")}${stored.handle ? ` as ${stored.handle}` : ""} through your team's Figma app, kept in ${store.where}; renewed before ${stored.expiresAt.slice(0, 10)}${auth.oauthAppOf(process.env) ? "" : style.warn(" (renewing needs FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET)")}`);
|
|
6060
|
+
print(lines.join("\n"));
|
|
6061
|
+
return stored || process.env.FIGMA_TOKEN ? 0 : 1;
|
|
6062
|
+
}
|
|
6063
|
+
if (action === "logout") {
|
|
6064
|
+
const removed = await store.delete();
|
|
6065
|
+
print(removed ? `${style.ok("signed out")}: the Figma sign-in is removed from ${store.where}. To end it in Figma as well, remove the token or the app in Figma > Settings > Security` : style.dim("not signed in: nothing to remove"));
|
|
6066
|
+
return 0;
|
|
6067
|
+
}
|
|
6068
|
+
const app = values.paste ? null : auth.oauthAppOf(process.env);
|
|
6069
|
+
if (app) {
|
|
6070
|
+
const port = values.port ? Number(values.port) : auth.DEFAULT_CALLBACK_PORT;
|
|
6071
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new UsageError(`--port ${JSON.stringify(values.port)}: expected a port from 1024 to 65535`);
|
|
6072
|
+
const credential2 = await auth.browserLogin({ app, port, say: (line) => print(style.dim(line)) });
|
|
6073
|
+
await store.set(credential2);
|
|
6074
|
+
print(`${style.ok("signed in")} to Figma${credential2.handle ? ` as ${credential2.handle}` : ""} through your team's app; kept in ${store.where}, renewed by itself`);
|
|
6075
|
+
return 0;
|
|
6076
|
+
}
|
|
6077
|
+
print([
|
|
6078
|
+
"Sign in with a Figma personal access token:",
|
|
6079
|
+
" 1. Figma > Settings > Security > Personal access tokens > Generate new token (opening it now)",
|
|
6080
|
+
" 2. Give it an expiry, and these scopes, read-only: File content, Library content, and Current user (to check it)",
|
|
6081
|
+
" 3. Paste it here; it is not shown",
|
|
6082
|
+
style.dim(` (A team can sign in through the browser instead: register a private Figma OAuth app with the redirect URL ${auth.callbackUrl(auth.DEFAULT_CALLBACK_PORT)}, then set FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET.)`)
|
|
6083
|
+
].join("\n"));
|
|
6084
|
+
if (process.stdin.isTTY) auth.openBrowser("https://www.figma.com/settings");
|
|
6085
|
+
const token = await auth.readSecret("Token: ");
|
|
6086
|
+
const { credential, checked } = await auth.tokenLogin(token);
|
|
6087
|
+
await store.set(credential);
|
|
6088
|
+
print(`${style.ok("signed in")} to Figma${credential.handle ? ` as ${credential.handle}` : ""}; kept in ${store.where}${checked ? "" : style.warn(" (not checked: give the token Current user: Read to let youiyoux check it)")}`);
|
|
6089
|
+
return 0;
|
|
6090
|
+
}
|
|
6043
6091
|
function builtHub() {
|
|
6044
6092
|
for (const dir of [join27(TOOL_ROOT, "hub"), join27(TOOL_ROOT, "apps/web/dist")]) if (existsSync4(join27(dir, "index.html"))) return dir;
|
|
6045
6093
|
return null;
|
|
@@ -6517,7 +6565,7 @@ async function cmdReport(values, positionals, io) {
|
|
|
6517
6565
|
return result.code;
|
|
6518
6566
|
}
|
|
6519
6567
|
async function cmdMcp(values) {
|
|
6520
|
-
const { startStdioServer } = await import("./src-
|
|
6568
|
+
const { startStdioServer } = await import("./src-LAUPNDCB.js");
|
|
6521
6569
|
await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
|
|
6522
6570
|
return -1;
|
|
6523
6571
|
}
|
|
@@ -6595,6 +6643,7 @@ var COMMANDS = {
|
|
|
6595
6643
|
upgrade: cmdUpgrade,
|
|
6596
6644
|
record: cmdRecord,
|
|
6597
6645
|
hub: cmdHubServe,
|
|
6646
|
+
figma: cmdFigma,
|
|
6598
6647
|
init: cmdInit,
|
|
6599
6648
|
sync: cmdSync,
|
|
6600
6649
|
scan: cmdScan,
|
|
@@ -700,7 +700,10 @@ var PoliteClient = class {
|
|
|
700
700
|
if (cached?.etag) headers["if-none-match"] = cached.etag;
|
|
701
701
|
if (cached?.lastModified) headers["if-modified-since"] = cached.lastModified;
|
|
702
702
|
if (this.options.token && parsed.hostname === "api.github.com") headers.authorization = `Bearer ${this.options.token}`;
|
|
703
|
-
if (this.options.figmaToken && parsed.hostname === "api.figma.com")
|
|
703
|
+
if (this.options.figmaToken && parsed.hostname === "api.figma.com") {
|
|
704
|
+
if (this.options.figmaBearer) headers.authorization = `Bearer ${this.options.figmaToken}`;
|
|
705
|
+
else headers["x-figma-token"] = this.options.figmaToken;
|
|
706
|
+
}
|
|
704
707
|
this.stats.sent++;
|
|
705
708
|
this.inFlight++;
|
|
706
709
|
this.stats.maxInFlight = Math.max(this.stats.maxInFlight, this.inFlight);
|
|
@@ -2354,7 +2357,7 @@ async function ingestLink(link, options) {
|
|
|
2354
2357
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2355
2358
|
const target = classifyLink(link, options);
|
|
2356
2359
|
const fetchedAt = now().toISOString();
|
|
2357
|
-
const result = await (target.kind === "github" ? readGithub(target, options.client, fetchedAt) : target.kind === "storybook" ? readStorybook(target, options.client, fetchedAt) : target.kind === "figma" ? options.figma ? readFigma(target, options.client, fetchedAt) : Promise.reject(new IngestError("a Figma file is read through Figma's API:
|
|
2360
|
+
const result = await (target.kind === "github" ? readGithub(target, options.client, fetchedAt) : target.kind === "storybook" ? readStorybook(target, options.client, fetchedAt) : target.kind === "figma" ? options.figma ? readFigma(target, options.client, fetchedAt) : Promise.reject(new IngestError("a Figma file is read through Figma's API: sign in with youiyoux figma login (or set FIGMA_TOKEN)")) : readDesignMd(target, options.client, fetchedAt, (path) => readFile3(path, "utf8")));
|
|
2358
2361
|
const over = result.entries.length - INGEST_CAP;
|
|
2359
2362
|
return cleanData({
|
|
2360
2363
|
...result,
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
|
|
2
|
+
import {
|
|
3
|
+
writeFileAtomic
|
|
4
|
+
} from "./chunk-S7UN5KQK.js";
|
|
5
|
+
import "./chunk-WNTS7ZO4.js";
|
|
6
|
+
|
|
7
|
+
// ../cli/src/figma-auth.ts
|
|
8
|
+
import { execFile, spawn } from "node:child_process";
|
|
9
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
10
|
+
import { chmod, mkdir, readFile, rm } from "node:fs/promises";
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { promisify } from "node:util";
|
|
15
|
+
var run = promisify(execFile);
|
|
16
|
+
var API = "https://api.figma.com/v1";
|
|
17
|
+
var FIGMA_SCOPES = ["file_content:read", "library_content:read", "current_user:read"];
|
|
18
|
+
var DEFAULT_CALLBACK_PORT = 47823;
|
|
19
|
+
var RENEW_MS = 24 * 60 * 60 * 1e3;
|
|
20
|
+
var KEYCHAIN = { account: "youiyoux", service: "youiyoux-figma" };
|
|
21
|
+
var parse = (text) => {
|
|
22
|
+
if (!text) return null;
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(text);
|
|
25
|
+
return value && (value.kind === "token" || value.kind === "oauth") ? value : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function fileStore(path) {
|
|
31
|
+
return {
|
|
32
|
+
where: path,
|
|
33
|
+
get: async () => parse(await readFile(path, "utf8").catch(() => null)),
|
|
34
|
+
set: async (credential) => {
|
|
35
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
36
|
+
await writeFileAtomic(path, `${JSON.stringify(credential)}
|
|
37
|
+
`);
|
|
38
|
+
await chmod(path, 384);
|
|
39
|
+
},
|
|
40
|
+
delete: async () => rm(path).then(() => true, () => false)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function keychainStore() {
|
|
44
|
+
const { account, service } = KEYCHAIN;
|
|
45
|
+
return {
|
|
46
|
+
where: "the macOS Keychain (youiyoux-figma)",
|
|
47
|
+
get: async () => parse(await run("security", ["find-generic-password", "-a", account, "-s", service, "-w"]).then((r) => Buffer.from(r.stdout.trim(), "base64").toString("utf8"), () => null)),
|
|
48
|
+
set: (credential) => new Promise((done, fail) => {
|
|
49
|
+
const child = spawn("security", ["-i"], { stdio: ["pipe", "ignore", "pipe"] });
|
|
50
|
+
let err = "";
|
|
51
|
+
child.stderr.on("data", (c) => err += String(c));
|
|
52
|
+
child.on("error", fail);
|
|
53
|
+
child.on("exit", (code) => code === 0 && !err.trim() ? done() : fail(new Error(`the Keychain refused the credential${err.trim() ? `: ${err.trim()}` : ""}`)));
|
|
54
|
+
child.stdin.end(`add-generic-password -a ${account} -s ${service} -U -w ${Buffer.from(JSON.stringify(credential)).toString("base64")}
|
|
55
|
+
`);
|
|
56
|
+
}),
|
|
57
|
+
delete: async () => run("security", ["delete-generic-password", "-a", account, "-s", service]).then(() => true, () => false)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function credentialStore(env = process.env, platform = process.platform) {
|
|
61
|
+
if (env.YOUI_FIGMA_CREDENTIALS) return fileStore(env.YOUI_FIGMA_CREDENTIALS);
|
|
62
|
+
if (platform === "darwin") return keychainStore();
|
|
63
|
+
return fileStore(join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "youiyoux", "figma.json"));
|
|
64
|
+
}
|
|
65
|
+
var basic = (id, secret) => `Basic ${Buffer.from(`${id}:${secret}`).toString("base64")}`;
|
|
66
|
+
async function postForm(fetcher, url, auth, form) {
|
|
67
|
+
const response = await fetcher(url, { method: "POST", headers: { authorization: auth, "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(form).toString(), signal: AbortSignal.timeout(15e3) });
|
|
68
|
+
const body = await response.json().catch(() => ({}));
|
|
69
|
+
const said = [body.message, body.err, body.error].find((v) => typeof v === "string");
|
|
70
|
+
if (!response.ok) throw new Error(`Figma refused (${response.status}): ${said ?? response.statusText}`);
|
|
71
|
+
return body;
|
|
72
|
+
}
|
|
73
|
+
async function whoAmI(token, bearer, fetcher = fetch) {
|
|
74
|
+
const response = await fetcher(`${API}/me`, { headers: bearer ? { authorization: `Bearer ${token}` } : { "x-figma-token": token }, signal: AbortSignal.timeout(15e3) });
|
|
75
|
+
if (!response.ok) return { ok: false, handle: null, status: response.status };
|
|
76
|
+
const me = await response.json().catch(() => ({}));
|
|
77
|
+
return { ok: true, handle: typeof me.handle === "string" ? me.handle : null, status: response.status };
|
|
78
|
+
}
|
|
79
|
+
var oauthAppOf = (env) => env.FIGMA_CLIENT_ID && env.FIGMA_CLIENT_SECRET ? { clientId: env.FIGMA_CLIENT_ID, clientSecret: env.FIGMA_CLIENT_SECRET } : null;
|
|
80
|
+
var callbackUrl = (port) => `http://127.0.0.1:${port}/callback`;
|
|
81
|
+
function openBrowser(url) {
|
|
82
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
83
|
+
spawn(command, args, { stdio: "ignore", detached: true }).on("error", () => {
|
|
84
|
+
}).unref();
|
|
85
|
+
}
|
|
86
|
+
var page = (title, text) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font:16px system-ui;margin:3rem;max-width:32rem"><h1 style="font-size:1.25rem">${title}</h1><p>${text}</p></body>`;
|
|
87
|
+
async function browserLogin(options) {
|
|
88
|
+
const fetcher = options.fetcher ?? fetch;
|
|
89
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
90
|
+
const port = options.port ?? DEFAULT_CALLBACK_PORT;
|
|
91
|
+
const redirect = callbackUrl(port);
|
|
92
|
+
const state = randomBytes(24).toString("base64url");
|
|
93
|
+
const verifier = randomBytes(48).toString("base64url");
|
|
94
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
95
|
+
const authorize = new URL("https://www.figma.com/oauth");
|
|
96
|
+
for (const [key, value] of Object.entries({ client_id: options.app.clientId, redirect_uri: redirect, scope: FIGMA_SCOPES.join(","), state, response_type: "code", code_challenge: challenge, code_challenge_method: "S256" })) authorize.searchParams.set(key, value);
|
|
97
|
+
const code = await new Promise((done, fail) => {
|
|
98
|
+
const server = createServer((req, res) => {
|
|
99
|
+
const url = new URL(req.url ?? "/", redirect);
|
|
100
|
+
if (url.pathname !== "/callback") {
|
|
101
|
+
res.writeHead(404).end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const finish = (status, title, text, outcome) => {
|
|
105
|
+
res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(page(title, text));
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
server.close();
|
|
108
|
+
outcome();
|
|
109
|
+
};
|
|
110
|
+
if (url.searchParams.get("state") !== state) return finish(400, "Not this sign-in", "This answer is not for the sign-in youiyoux started. Run youiyoux figma login again.", () => fail(new Error("the answer came with another sign-in's state; nothing was kept")));
|
|
111
|
+
const error = url.searchParams.get("error");
|
|
112
|
+
if (error) return finish(400, "Not signed in", "Figma did not sign you in. You can close this tab.", () => fail(new Error(`Figma did not sign you in: ${error}`)));
|
|
113
|
+
const given = url.searchParams.get("code");
|
|
114
|
+
if (!given) return finish(400, "Not signed in", "Figma sent no code. Run youiyoux figma login again.", () => fail(new Error("Figma sent no code")));
|
|
115
|
+
finish(200, "Signed in to Figma", "youiyoux can read your Figma files now. You can close this tab.", () => done(given));
|
|
116
|
+
});
|
|
117
|
+
const timer = setTimeout(() => {
|
|
118
|
+
server.close();
|
|
119
|
+
fail(new Error("no answer from Figma within five minutes; run youiyoux figma login again"));
|
|
120
|
+
}, options.timeoutMs ?? 5 * 6e4);
|
|
121
|
+
server.once("error", (error) => {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
fail(new Error(error.code === "EADDRINUSE" ? `port ${port} is taken: pass --port <n>, and register ${callbackUrl(port).replace(String(port), "<n>")} in the Figma app` : error.message));
|
|
124
|
+
});
|
|
125
|
+
server.listen(port, "127.0.0.1", () => {
|
|
126
|
+
options.say?.(`Opening Figma to sign in. If no browser opens, visit:
|
|
127
|
+
${authorize.href}`);
|
|
128
|
+
(options.open ?? openBrowser)(authorize.href);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
const tokens = await postForm(fetcher, `${API}/oauth/token`, basic(options.app.clientId, options.app.clientSecret), { redirect_uri: redirect, code, grant_type: "authorization_code", code_verifier: verifier });
|
|
132
|
+
const accessToken = tokens.access_token;
|
|
133
|
+
const refreshToken = tokens.refresh_token;
|
|
134
|
+
if (typeof accessToken !== "string" || typeof refreshToken !== "string") throw new Error("Figma's answer had no tokens");
|
|
135
|
+
const seconds = typeof tokens.expires_in === "number" ? tokens.expires_in : 90 * 86400;
|
|
136
|
+
const me = await whoAmI(accessToken, true, fetcher).catch(() => ({ ok: false, handle: null, status: 0 }));
|
|
137
|
+
return { kind: "oauth", accessToken, refreshToken, expiresAt: new Date(now().getTime() + seconds * 1e3).toISOString(), clientId: options.app.clientId, savedAt: now().toISOString(), handle: me.handle };
|
|
138
|
+
}
|
|
139
|
+
async function tokenLogin(token, options = {}) {
|
|
140
|
+
const clean = token.trim();
|
|
141
|
+
if (!/^[\w-]{20,200}$/.test(clean)) throw new Error("that does not look like a Figma personal access token (figd_\u2026)");
|
|
142
|
+
const me = await whoAmI(clean, false, options.fetcher);
|
|
143
|
+
if (!me.ok && me.status !== 403) throw new Error(`Figma does not accept that token (${me.status}): make a new one`);
|
|
144
|
+
return { credential: { kind: "token", token: clean, savedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(), handle: me.handle }, checked: me.ok };
|
|
145
|
+
}
|
|
146
|
+
async function figmaAccess(options = {}) {
|
|
147
|
+
const env = options.env ?? process.env;
|
|
148
|
+
if (env.FIGMA_TOKEN) return { token: env.FIGMA_TOKEN, bearer: false, from: "env" };
|
|
149
|
+
const store = options.store ?? credentialStore(env);
|
|
150
|
+
const stored = await store.get();
|
|
151
|
+
if (!stored) return null;
|
|
152
|
+
if (stored.kind === "token") return { token: stored.token, bearer: false, from: "token" };
|
|
153
|
+
const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
154
|
+
if (Date.parse(stored.expiresAt) - now.getTime() > RENEW_MS) return { token: stored.accessToken, bearer: true, from: "oauth" };
|
|
155
|
+
const app = oauthAppOf(env);
|
|
156
|
+
if (!app || app.clientId !== stored.clientId) {
|
|
157
|
+
if (Date.parse(stored.expiresAt) > now.getTime()) return { token: stored.accessToken, bearer: true, from: "oauth" };
|
|
158
|
+
throw new Error("your Figma sign-in has lapsed, and renewing it needs your team's Figma app (FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET): set them and run youiyoux figma login");
|
|
159
|
+
}
|
|
160
|
+
const renewed = await postForm(options.fetcher ?? fetch, `${API}/oauth/refresh`, basic(app.clientId, app.clientSecret), { refresh_token: stored.refreshToken });
|
|
161
|
+
if (typeof renewed.access_token !== "string") throw new Error("Figma did not renew the sign-in: run youiyoux figma login");
|
|
162
|
+
const seconds = typeof renewed.expires_in === "number" ? renewed.expires_in : 90 * 86400;
|
|
163
|
+
await store.set({ ...stored, accessToken: renewed.access_token, expiresAt: new Date(now.getTime() + seconds * 1e3).toISOString() });
|
|
164
|
+
return { token: renewed.access_token, bearer: true, from: "oauth" };
|
|
165
|
+
}
|
|
166
|
+
async function readSecret(prompt, input = process.stdin, output = process.stderr) {
|
|
167
|
+
if (!input.isTTY) {
|
|
168
|
+
let text = "";
|
|
169
|
+
for await (const chunk of input) {
|
|
170
|
+
text += String(chunk);
|
|
171
|
+
if (text.includes("\n")) break;
|
|
172
|
+
}
|
|
173
|
+
return text.split(/\r?\n/)[0] ?? "";
|
|
174
|
+
}
|
|
175
|
+
output.write(prompt);
|
|
176
|
+
input.setRawMode(true);
|
|
177
|
+
input.resume();
|
|
178
|
+
return new Promise((done, fail) => {
|
|
179
|
+
let value = "";
|
|
180
|
+
const onData = (chunk) => {
|
|
181
|
+
for (const char of chunk.toString("utf8")) {
|
|
182
|
+
if (char === "\r" || char === "\n") return finish(() => done(value));
|
|
183
|
+
if (char === "") return finish(() => fail(new Error("cancelled")));
|
|
184
|
+
if (char === "\x7F" || char === "\b") value = value.slice(0, -1);
|
|
185
|
+
else if (char >= " ") value += char;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const finish = (then) => {
|
|
189
|
+
input.off("data", onData);
|
|
190
|
+
input.setRawMode(false);
|
|
191
|
+
input.pause();
|
|
192
|
+
output.write("\n");
|
|
193
|
+
then();
|
|
194
|
+
};
|
|
195
|
+
input.on("data", onData);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
DEFAULT_CALLBACK_PORT,
|
|
200
|
+
FIGMA_SCOPES,
|
|
201
|
+
browserLogin,
|
|
202
|
+
callbackUrl,
|
|
203
|
+
credentialStore,
|
|
204
|
+
figmaAccess,
|
|
205
|
+
fileStore,
|
|
206
|
+
keychainStore,
|
|
207
|
+
oauthAppOf,
|
|
208
|
+
openBrowser,
|
|
209
|
+
readSecret,
|
|
210
|
+
tokenLogin,
|
|
211
|
+
whoAmI
|
|
212
|
+
};
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
plural,
|
|
5
5
|
style,
|
|
6
6
|
workflowStateDir
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ED35SP3F.js";
|
|
8
8
|
import "./chunk-HII2I2PI.js";
|
|
9
9
|
import "./chunk-42YJX3W5.js";
|
|
10
10
|
import "./chunk-HIXGUALJ.js";
|
|
@@ -14,12 +14,12 @@ import "./chunk-WNTS7ZO4.js";
|
|
|
14
14
|
// ../cli/src/ingest.ts
|
|
15
15
|
import { userInfo } from "node:os";
|
|
16
16
|
async function readLink(link, options) {
|
|
17
|
-
const { githubBuckets, ingestLink, PoliteClient } = await import("./src-
|
|
17
|
+
const { githubBuckets, ingestLink, PoliteClient } = await import("./src-FBTLHBVB.js");
|
|
18
18
|
const env = options.env ?? process.env;
|
|
19
19
|
const token = env.GITHUB_TOKEN || void 0;
|
|
20
|
-
const
|
|
21
|
-
const client = new PoliteClient({ buckets: githubBuckets(token), ...token ? { token } : {}, ...
|
|
22
|
-
const result = await ingestLink(link, { client, allowLocal: options.allowLocal, figma: Boolean(
|
|
20
|
+
const figma = /figma\.com\//i.test(link) ? await (await import("./figma-auth-ODUC3TSR.js")).figmaAccess({ env }) : null;
|
|
21
|
+
const client = new PoliteClient({ buckets: githubBuckets(token), ...token ? { token } : {}, ...figma ? { figmaToken: figma.token, figmaBearer: figma.bearer } : {} });
|
|
22
|
+
const result = await ingestLink(link, { client, allowLocal: options.allowLocal, figma: Boolean(figma) });
|
|
23
23
|
return { link: result.link, source: result.source, license: result.license, fetchedAt: result.fetchedAt, entries: result.entries, skipped: result.skipped, notes: result.notes };
|
|
24
24
|
}
|
|
25
25
|
async function fileIngestDraft(project, reading, state) {
|
package/dist/hub-previews.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
discoverCacheDir,
|
|
8
8
|
loadIndex
|
|
9
|
-
} from "./chunks/chunk-
|
|
9
|
+
} from "./chunks/chunk-XSIH7RR4.js";
|
|
10
10
|
import "./chunks/chunk-A2IZOTJG.js";
|
|
11
11
|
import {
|
|
12
12
|
THUMBS_PATH,
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
selectCandidates,
|
|
18
18
|
thumbsDir,
|
|
19
19
|
writeThumbIndex
|
|
20
|
-
} from "./chunks/chunk-
|
|
21
|
-
import "./chunks/chunk-
|
|
20
|
+
} from "./chunks/chunk-FO3WR47A.js";
|
|
21
|
+
import "./chunks/chunk-3SBSYNAG.js";
|
|
22
22
|
import "./chunks/chunk-FVVL5JCY.js";
|
|
23
23
|
import "./chunks/chunk-S7UN5KQK.js";
|
|
24
24
|
import "./chunks/chunk-WNTS7ZO4.js";
|
package/dist/hub-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
|
|
3
3
|
import {
|
|
4
4
|
discoverThumbsMiddleware
|
|
5
|
-
} from "./chunks/chunk-
|
|
5
|
+
} from "./chunks/chunk-FO3WR47A.js";
|
|
6
6
|
import {
|
|
7
7
|
isLocalHost,
|
|
8
8
|
loadPackage,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
readTryIndex,
|
|
11
11
|
send,
|
|
12
12
|
tryMiddleware
|
|
13
|
-
} from "./chunks/chunk-
|
|
13
|
+
} from "./chunks/chunk-3SBSYNAG.js";
|
|
14
14
|
import "./chunks/chunk-FVVL5JCY.js";
|
|
15
15
|
import "./chunks/chunk-S7UN5KQK.js";
|
|
16
16
|
import "./chunks/chunk-WNTS7ZO4.js";
|
package/dist/hub-snapshot.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
|
|
|
3
3
|
import {
|
|
4
4
|
loadPackage,
|
|
5
5
|
tryProject
|
|
6
|
-
} from "./chunks/chunk-
|
|
6
|
+
} from "./chunks/chunk-3SBSYNAG.js";
|
|
7
7
|
import "./chunks/chunk-FVVL5JCY.js";
|
|
8
8
|
import "./chunks/chunk-WNTS7ZO4.js";
|
|
9
9
|
|
package/dist/youi.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
|
|
3
3
|
import {
|
|
4
4
|
main
|
|
5
|
-
} from "./chunks/chunk-
|
|
6
|
-
import "./chunks/chunk-
|
|
5
|
+
} from "./chunks/chunk-WMIJNZIL.js";
|
|
6
|
+
import "./chunks/chunk-ED35SP3F.js";
|
|
7
7
|
import "./chunks/chunk-HII2I2PI.js";
|
|
8
8
|
import "./chunks/chunk-42YJX3W5.js";
|
|
9
9
|
import "./chunks/chunk-ALQVHSF2.js";
|
|
10
|
-
import "./chunks/chunk-
|
|
10
|
+
import "./chunks/chunk-XSIH7RR4.js";
|
|
11
11
|
import "./chunks/chunk-A2IZOTJG.js";
|
|
12
12
|
import "./chunks/chunk-FVVL5JCY.js";
|
|
13
13
|
import "./chunks/chunk-HIXGUALJ.js";
|
package/dist/youiyoux.js
CHANGED
|
@@ -3,12 +3,12 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
|
|
|
3
3
|
import {
|
|
4
4
|
COMMAND_NAMES,
|
|
5
5
|
main
|
|
6
|
-
} from "./chunks/chunk-
|
|
7
|
-
import "./chunks/chunk-
|
|
6
|
+
} from "./chunks/chunk-WMIJNZIL.js";
|
|
7
|
+
import "./chunks/chunk-ED35SP3F.js";
|
|
8
8
|
import "./chunks/chunk-HII2I2PI.js";
|
|
9
9
|
import "./chunks/chunk-42YJX3W5.js";
|
|
10
10
|
import "./chunks/chunk-ALQVHSF2.js";
|
|
11
|
-
import "./chunks/chunk-
|
|
11
|
+
import "./chunks/chunk-XSIH7RR4.js";
|
|
12
12
|
import "./chunks/chunk-A2IZOTJG.js";
|
|
13
13
|
import "./chunks/chunk-FVVL5JCY.js";
|
|
14
14
|
import "./chunks/chunk-HIXGUALJ.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "youiyoux",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Your agent rebuilds components you already have. youiyoux catches it: npx youiyoux turns any repo into a shareable report of near-duplicate components, hardcoded token values and off-library imports, with the fixes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"typescript": "6.0.3"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@youiyoux/cli": "0.1.
|
|
50
|
-
"@youiyoux/
|
|
51
|
-
"@youiyoux/
|
|
49
|
+
"@youiyoux/cli": "0.1.16",
|
|
50
|
+
"@youiyoux/scanner": "0.1.16",
|
|
51
|
+
"@youiyoux/core": "0.1.16"
|
|
52
52
|
},
|
|
53
53
|
"exports": {
|
|
54
54
|
"./package.json": "./package.json"
|