@guidelytech/flags 0.0.0-stage → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +181 -2
- package/dist/chunk-2RZKAT37.js +1085 -0
- package/dist/chunk-2RZKAT37.js.map +1 -0
- package/dist/chunk-OY4QS3WL.js +804 -0
- package/dist/chunk-OY4QS3WL.js.map +1 -0
- package/dist/chunk-ZTY7K7P2.js +988 -0
- package/dist/chunk-ZTY7K7P2.js.map +1 -0
- package/dist/cli.cjs +2010 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +229 -0
- package/dist/cli.js.map +1 -0
- package/dist/evaluate.cjs +835 -0
- package/dist/evaluate.cjs.map +1 -0
- package/dist/evaluate.d.cts +283 -0
- package/dist/evaluate.d.ts +283 -0
- package/dist/evaluate.js +57 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/index.cjs +1988 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +355 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_FLAGS_API_URL,
|
|
4
|
+
createFlagsClient,
|
|
5
|
+
decryptPayloadString,
|
|
6
|
+
isEncryptedPayload,
|
|
7
|
+
isFlagsPayload
|
|
8
|
+
} from "./chunk-2RZKAT37.js";
|
|
9
|
+
import "./chunk-OY4QS3WL.js";
|
|
10
|
+
|
|
11
|
+
// src/cli-main.ts
|
|
12
|
+
import { resolve } from "path";
|
|
13
|
+
|
|
14
|
+
// src/code-refs.ts
|
|
15
|
+
import { execFileSync } from "child_process";
|
|
16
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
17
|
+
import { join, relative, sep } from "path";
|
|
18
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "vendor", ".next", ".svelte-kit", ".turbo", "coverage", "target", "out"]);
|
|
19
|
+
var SKIP_FILES = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|composer\.lock|Cargo\.lock|Gemfile\.lock|poetry\.lock)$|\.(min\.js|map|png|jpe?g|gif|webp|ico|svg|pdf|zip|gz|woff2?|ttf|eot|mp4|mov|mp3|jar|class|so|dylib|dll|exe|bin)$/i;
|
|
20
|
+
var MAX_FILE_BYTES = 1024 * 1024;
|
|
21
|
+
var MAX_REFS = 1e4;
|
|
22
|
+
function listFiles(dir) {
|
|
23
|
+
try {
|
|
24
|
+
const out = execFileSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 256 * 1024 * 1024 });
|
|
25
|
+
const files2 = out.split("\0").filter(Boolean);
|
|
26
|
+
if (files2.length) return files2.filter((f) => !f.split("/").some((p) => SKIP_DIRS.has(p)));
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
const files = [];
|
|
30
|
+
const walk = (abs) => {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = readdirSync(abs, { withFileTypes: true });
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const e of entries) {
|
|
38
|
+
if (e.isDirectory()) {
|
|
39
|
+
if (!SKIP_DIRS.has(e.name)) walk(join(abs, e.name));
|
|
40
|
+
} else if (e.isFile()) files.push(relative(dir, join(abs, e.name)).split(sep).join("/"));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walk(dir);
|
|
44
|
+
return files.sort();
|
|
45
|
+
}
|
|
46
|
+
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
function scanFiles(dir, files, flagKeys) {
|
|
48
|
+
const keys = Array.from(new Set(flagKeys.filter(Boolean))).sort((a, b) => b.length - a.length);
|
|
49
|
+
if (!keys.length) return [];
|
|
50
|
+
const re = new RegExp(`(['"\`])(${keys.map(escapeRe).join("|")})\\1`, "g");
|
|
51
|
+
const refs = [];
|
|
52
|
+
for (const path of files) {
|
|
53
|
+
if (SKIP_FILES.test(path)) continue;
|
|
54
|
+
const abs = join(dir, path);
|
|
55
|
+
let text;
|
|
56
|
+
try {
|
|
57
|
+
const st = statSync(abs);
|
|
58
|
+
if (!st.isFile() || st.size > MAX_FILE_BYTES) continue;
|
|
59
|
+
const buf = readFileSync(abs);
|
|
60
|
+
if (buf.subarray(0, 8e3).includes(0)) continue;
|
|
61
|
+
text = buf.toString("utf8");
|
|
62
|
+
} catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!keys.some((k) => text.includes(k))) continue;
|
|
66
|
+
const lines = text.split(/\r?\n/);
|
|
67
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68
|
+
const line = lines[i];
|
|
69
|
+
re.lastIndex = 0;
|
|
70
|
+
const seen = /* @__PURE__ */ new Set();
|
|
71
|
+
for (let m = re.exec(line); m; m = re.exec(line)) {
|
|
72
|
+
const flag = m[2];
|
|
73
|
+
if (seen.has(flag)) continue;
|
|
74
|
+
seen.add(flag);
|
|
75
|
+
refs.push({ flag, path, line: i + 1, snippet: line.trim().slice(0, 200) });
|
|
76
|
+
if (refs.length >= MAX_REFS) return refs;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return refs;
|
|
81
|
+
}
|
|
82
|
+
function gitValue(dir, args) {
|
|
83
|
+
try {
|
|
84
|
+
return execFileSync("git", args, { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || void 0;
|
|
85
|
+
} catch {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/cli-main.ts
|
|
91
|
+
var HELP = `guidely-flags \u2014 Guidely feature flag tools
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
guidely-flags code-refs --key gly_sk_\u2026 --environment production --repo <name> [options]
|
|
95
|
+
guidely-flags evaluate --key <pk_\u2026|gly_sk_\u2026> [--environment <env>] [--flag <key>] [--attributes '{"id":"u1"}']
|
|
96
|
+
|
|
97
|
+
code-refs: finds flag keys used as quoted string literals in your code and uploads the
|
|
98
|
+
references, so the dashboard shows where each flag is used (run it in CI on your main branch).
|
|
99
|
+
--api-url <url> Guidely URL (default ${DEFAULT_FLAGS_API_URL}; env GUIDELY_API_URL)
|
|
100
|
+
--key <gly_sk_\u2026> Secret API key with flags:read and flags:code-refs (env GUIDELY_API_KEY)
|
|
101
|
+
--environment <env> development | staging | production (env GUIDELY_ENVIRONMENT)
|
|
102
|
+
--repo <name> Repository name, e.g. acme/web (env GITHUB_REPOSITORY)
|
|
103
|
+
--branch <branch> Default: the current git branch
|
|
104
|
+
--commit <sha> Default: the current git commit
|
|
105
|
+
--dir <path> Directory to scan (default .)
|
|
106
|
+
--decryption-key <key> For environments with encrypted payloads
|
|
107
|
+
--dry-run Print the references instead of uploading them
|
|
108
|
+
|
|
109
|
+
evaluate: prints how a flag (or every flag) evaluates for some attributes.
|
|
110
|
+
--flag <key> Flag key (default: all flags)
|
|
111
|
+
--attributes <json> Attributes object (default {})
|
|
112
|
+
|
|
113
|
+
Exit codes: 0 success, 1 failure, 2 usage error.
|
|
114
|
+
`;
|
|
115
|
+
var UsageError = class extends Error {
|
|
116
|
+
};
|
|
117
|
+
function parseArgs(argv) {
|
|
118
|
+
const flags = {};
|
|
119
|
+
let command;
|
|
120
|
+
for (let i = 0; i < argv.length; i++) {
|
|
121
|
+
const a = argv[i];
|
|
122
|
+
if (a.startsWith("--")) {
|
|
123
|
+
const eq = a.indexOf("=");
|
|
124
|
+
if (eq > 0) flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
125
|
+
else if (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("--")) flags[a.slice(2)] = argv[++i];
|
|
126
|
+
else flags[a.slice(2)] = true;
|
|
127
|
+
} else if (a === "-h") flags.help = true;
|
|
128
|
+
else if (!command) command = a;
|
|
129
|
+
else throw new UsageError(`Unexpected argument: ${a}`);
|
|
130
|
+
}
|
|
131
|
+
return { command, flags };
|
|
132
|
+
}
|
|
133
|
+
async function main(argv, io = {}) {
|
|
134
|
+
const env = io.env ?? process.env;
|
|
135
|
+
const out = io.out ?? ((t) => process.stdout.write(t));
|
|
136
|
+
const err = io.err ?? ((t) => process.stderr.write(t));
|
|
137
|
+
const doFetch = io.fetch ?? fetch;
|
|
138
|
+
try {
|
|
139
|
+
const { command, flags } = parseArgs(argv);
|
|
140
|
+
if (!command || flags.help || command === "help") {
|
|
141
|
+
out(HELP);
|
|
142
|
+
return command || flags.help ? 0 : 2;
|
|
143
|
+
}
|
|
144
|
+
const str = (name, envName) => {
|
|
145
|
+
const v = flags[name];
|
|
146
|
+
if (v === true) throw new UsageError(`--${name} needs a value`);
|
|
147
|
+
return v ?? (envName ? env[envName] : void 0) ?? void 0;
|
|
148
|
+
};
|
|
149
|
+
const apiUrl = (str("api-url", "GUIDELY_API_URL") || DEFAULT_FLAGS_API_URL).replace(/\/+$/, "");
|
|
150
|
+
const key = str("key", "GUIDELY_API_KEY");
|
|
151
|
+
const environment = str("environment", "GUIDELY_ENVIRONMENT");
|
|
152
|
+
if (!key) throw new UsageError("--key is required");
|
|
153
|
+
if (command === "code-refs") {
|
|
154
|
+
if (!key.startsWith("gly_sk_")) throw new UsageError("code-refs needs a secret gly_sk_ API key");
|
|
155
|
+
if (!environment) throw new UsageError("--environment is required");
|
|
156
|
+
const dir = resolve(io.cwd ?? process.cwd(), str("dir") ?? ".");
|
|
157
|
+
const repo = str("repo", "GITHUB_REPOSITORY");
|
|
158
|
+
if (!repo) throw new UsageError("--repo is required");
|
|
159
|
+
const branch = str("branch") ?? env.GITHUB_HEAD_REF ?? env.GITHUB_REF_NAME ?? gitValue(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
160
|
+
if (!branch) throw new UsageError("--branch is required (not a git checkout)");
|
|
161
|
+
const commit = str("commit") ?? env.GITHUB_SHA ?? gitValue(dir, ["rev-parse", "HEAD"]);
|
|
162
|
+
const auth = { Authorization: `Bearer ${key}`, Accept: "application/json" };
|
|
163
|
+
const res = await doFetch(`${apiUrl}/api/v1/sdk/flags?environment=${encodeURIComponent(environment)}`, { headers: auth });
|
|
164
|
+
if (!res.ok) throw new Error(`Downloading flags failed (${res.status}): ${(await res.text()).slice(0, 300)}`);
|
|
165
|
+
let payload = await res.json();
|
|
166
|
+
if (isEncryptedPayload(payload)) {
|
|
167
|
+
const dk = str("decryption-key", "GUIDELY_DECRYPTION_KEY");
|
|
168
|
+
if (!dk) throw new Error("The payload is encrypted: pass --decryption-key");
|
|
169
|
+
payload = { ...payload, ...JSON.parse(await decryptPayloadString(payload.encryptedPayload, dk)) };
|
|
170
|
+
}
|
|
171
|
+
if (!isFlagsPayload(payload)) throw new Error("Unexpected flags payload");
|
|
172
|
+
const keys = Object.keys(payload.flags);
|
|
173
|
+
const files = listFiles(dir);
|
|
174
|
+
const refs = scanFiles(dir, files, keys);
|
|
175
|
+
if (refs.length >= MAX_REFS) err(`Stopped after ${MAX_REFS} references.
|
|
176
|
+
`);
|
|
177
|
+
if (flags["dry-run"]) {
|
|
178
|
+
for (const r of refs) out(`${r.flag} ${r.path}:${r.line} ${r.snippet}
|
|
179
|
+
`);
|
|
180
|
+
out(`${refs.length} reference(s) to ${new Set(refs.map((r) => r.flag)).size} of ${keys.length} flag(s) in ${files.length} file(s) (dry run, nothing uploaded)
|
|
181
|
+
`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
const up = await doFetch(`${apiUrl}/api/v1/flags/code-refs`, { method: "POST", headers: { ...auth, "Content-Type": "application/json" }, body: JSON.stringify({ repo, branch, ...commit ? { commit } : {}, refs }) });
|
|
185
|
+
if (!up.ok) throw new Error(`Uploading code references failed (${up.status}): ${(await up.text()).slice(0, 300)}`);
|
|
186
|
+
out(`Uploaded ${refs.length} reference(s) to ${new Set(refs.map((r) => r.flag)).size} flag(s) for ${repo}@${branch}
|
|
187
|
+
`);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
if (command === "evaluate") {
|
|
191
|
+
let attributes = {};
|
|
192
|
+
const raw = str("attributes");
|
|
193
|
+
if (raw) {
|
|
194
|
+
try {
|
|
195
|
+
attributes = JSON.parse(raw);
|
|
196
|
+
} catch {
|
|
197
|
+
throw new UsageError("--attributes must be a JSON object");
|
|
198
|
+
}
|
|
199
|
+
if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) throw new UsageError("--attributes must be a JSON object");
|
|
200
|
+
}
|
|
201
|
+
let failure = null;
|
|
202
|
+
const client = createFlagsClient({ sdkKey: key, apiUrl, environment, pollingMode: "manual", reportUsage: false, trackExposures: false, storage: false, logger: null, fetch: doFetch, decryptionKey: str("decryption-key", "GUIDELY_DECRYPTION_KEY"), onError: (e) => failure = e });
|
|
203
|
+
await client.refresh();
|
|
204
|
+
client.destroy();
|
|
205
|
+
if (!client.getPayload()) throw failure ?? new Error("Could not load flags");
|
|
206
|
+
const user = client.forUser(attributes);
|
|
207
|
+
const flag = str("flag");
|
|
208
|
+
const result = flag ? user.getDetails(flag, null) : user.getAll();
|
|
209
|
+
out(`${JSON.stringify(result, null, 2)}
|
|
210
|
+
`);
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
throw new UsageError(`Unknown command: ${command}`);
|
|
214
|
+
} catch (e) {
|
|
215
|
+
if (e instanceof UsageError) {
|
|
216
|
+
err(`${e.message}
|
|
217
|
+
|
|
218
|
+
${HELP}`);
|
|
219
|
+
return 2;
|
|
220
|
+
}
|
|
221
|
+
err(`guidely-flags: ${e instanceof Error ? e.message : String(e)}
|
|
222
|
+
`);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/cli.ts
|
|
228
|
+
main(process.argv.slice(2)).then((code) => process.exit(code));
|
|
229
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli-main.ts","../src/code-refs.ts","../src/cli.ts"],"sourcesContent":["/** `guidely-flags` command line (Node only). Kept separate from `cli.ts` so tests can call {@link main}. */\nimport { resolve } from \"node:path\";\nimport { createFlagsClient, DEFAULT_FLAGS_API_URL } from \"./client\";\nimport { gitValue, listFiles, scanFiles, MAX_REFS, type CodeRef } from \"./code-refs\";\nimport { decryptPayloadString } from \"./crypto\";\nimport type { FlagsPayload } from \"./types\";\nimport { isEncryptedPayload, isFlagsPayload } from \"./util\";\n\nexport const HELP = `guidely-flags — Guidely feature flag tools\n\nUsage:\n guidely-flags code-refs --key gly_sk_… --environment production --repo <name> [options]\n guidely-flags evaluate --key <pk_…|gly_sk_…> [--environment <env>] [--flag <key>] [--attributes '{\"id\":\"u1\"}']\n\ncode-refs: finds flag keys used as quoted string literals in your code and uploads the\nreferences, so the dashboard shows where each flag is used (run it in CI on your main branch).\n --api-url <url> Guidely URL (default ${DEFAULT_FLAGS_API_URL}; env GUIDELY_API_URL)\n --key <gly_sk_…> Secret API key with flags:read and flags:code-refs (env GUIDELY_API_KEY)\n --environment <env> development | staging | production (env GUIDELY_ENVIRONMENT)\n --repo <name> Repository name, e.g. acme/web (env GITHUB_REPOSITORY)\n --branch <branch> Default: the current git branch\n --commit <sha> Default: the current git commit\n --dir <path> Directory to scan (default .)\n --decryption-key <key> For environments with encrypted payloads\n --dry-run Print the references instead of uploading them\n\nevaluate: prints how a flag (or every flag) evaluates for some attributes.\n --flag <key> Flag key (default: all flags)\n --attributes <json> Attributes object (default {})\n\nExit codes: 0 success, 1 failure, 2 usage error.\n`;\n\nexport interface CliIo {\n fetch?: typeof fetch;\n env?: Record<string, string | undefined>;\n out?: (text: string) => void;\n err?: (text: string) => void;\n cwd?: string;\n}\n\nclass UsageError extends Error {}\n\nfunction parseArgs(argv: string[]): { command?: string; flags: Record<string, string | true> } {\n const flags: Record<string, string | true> = {};\n let command: string | undefined;\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!;\n if (a.startsWith(\"--\")) {\n const eq = a.indexOf(\"=\");\n if (eq > 0) flags[a.slice(2, eq)] = a.slice(eq + 1);\n else if (argv[i + 1] !== undefined && !argv[i + 1]!.startsWith(\"--\")) flags[a.slice(2)] = argv[++i]!;\n else flags[a.slice(2)] = true;\n } else if (a === \"-h\") flags.help = true;\n else if (!command) command = a;\n else throw new UsageError(`Unexpected argument: ${a}`);\n }\n return { command, flags };\n}\n\nexport async function main(argv: string[], io: CliIo = {}): Promise<number> {\n const env = io.env ?? process.env;\n const out = io.out ?? ((t: string) => process.stdout.write(t));\n const err = io.err ?? ((t: string) => process.stderr.write(t));\n const doFetch = io.fetch ?? fetch;\n try {\n const { command, flags } = parseArgs(argv);\n if (!command || flags.help || command === \"help\") {\n out(HELP);\n return command || flags.help ? 0 : 2;\n }\n const str = (name: string, envName?: string): string | undefined => {\n const v = flags[name];\n if (v === true) throw new UsageError(`--${name} needs a value`);\n return v ?? (envName ? env[envName] : undefined) ?? undefined;\n };\n const apiUrl = (str(\"api-url\", \"GUIDELY_API_URL\") || DEFAULT_FLAGS_API_URL).replace(/\\/+$/, \"\");\n const key = str(\"key\", \"GUIDELY_API_KEY\");\n const environment = str(\"environment\", \"GUIDELY_ENVIRONMENT\");\n if (!key) throw new UsageError(\"--key is required\");\n\n if (command === \"code-refs\") {\n if (!key.startsWith(\"gly_sk_\")) throw new UsageError(\"code-refs needs a secret gly_sk_ API key\");\n if (!environment) throw new UsageError(\"--environment is required\");\n const dir = resolve(io.cwd ?? process.cwd(), str(\"dir\") ?? \".\");\n const repo = str(\"repo\", \"GITHUB_REPOSITORY\");\n if (!repo) throw new UsageError(\"--repo is required\");\n const branch = str(\"branch\") ?? env.GITHUB_HEAD_REF ?? env.GITHUB_REF_NAME ?? gitValue(dir, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n if (!branch) throw new UsageError(\"--branch is required (not a git checkout)\");\n const commit = str(\"commit\") ?? env.GITHUB_SHA ?? gitValue(dir, [\"rev-parse\", \"HEAD\"]);\n\n const auth = { Authorization: `Bearer ${key}`, Accept: \"application/json\" };\n const res = await doFetch(`${apiUrl}/api/v1/sdk/flags?environment=${encodeURIComponent(environment)}`, { headers: auth });\n if (!res.ok) throw new Error(`Downloading flags failed (${res.status}): ${(await res.text()).slice(0, 300)}`);\n let payload = (await res.json()) as unknown;\n if (isEncryptedPayload(payload)) {\n const dk = str(\"decryption-key\", \"GUIDELY_DECRYPTION_KEY\");\n if (!dk) throw new Error(\"The payload is encrypted: pass --decryption-key\");\n payload = { ...payload, ...JSON.parse(await decryptPayloadString(payload.encryptedPayload, dk)) };\n }\n if (!isFlagsPayload(payload)) throw new Error(\"Unexpected flags payload\");\n const keys = Object.keys((payload as FlagsPayload).flags);\n const files = listFiles(dir);\n const refs: CodeRef[] = scanFiles(dir, files, keys);\n if (refs.length >= MAX_REFS) err(`Stopped after ${MAX_REFS} references.\\n`);\n if (flags[\"dry-run\"]) {\n for (const r of refs) out(`${r.flag}\\t${r.path}:${r.line}\\t${r.snippet}\\n`);\n out(`${refs.length} reference(s) to ${new Set(refs.map((r) => r.flag)).size} of ${keys.length} flag(s) in ${files.length} file(s) (dry run, nothing uploaded)\\n`);\n return 0;\n }\n const up = await doFetch(`${apiUrl}/api/v1/flags/code-refs`, { method: \"POST\", headers: { ...auth, \"Content-Type\": \"application/json\" }, body: JSON.stringify({ repo, branch, ...(commit ? { commit } : {}), refs }) });\n if (!up.ok) throw new Error(`Uploading code references failed (${up.status}): ${(await up.text()).slice(0, 300)}`);\n out(`Uploaded ${refs.length} reference(s) to ${new Set(refs.map((r) => r.flag)).size} flag(s) for ${repo}@${branch}\\n`);\n return 0;\n }\n\n if (command === \"evaluate\") {\n let attributes: Record<string, unknown> = {};\n const raw = str(\"attributes\");\n if (raw) {\n try {\n attributes = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new UsageError(\"--attributes must be a JSON object\");\n }\n if (!attributes || typeof attributes !== \"object\" || Array.isArray(attributes)) throw new UsageError(\"--attributes must be a JSON object\");\n }\n let failure: Error | null = null;\n const client = createFlagsClient({ sdkKey: key, apiUrl, environment, pollingMode: \"manual\", reportUsage: false, trackExposures: false, storage: false, logger: null, fetch: doFetch, decryptionKey: str(\"decryption-key\", \"GUIDELY_DECRYPTION_KEY\"), onError: (e) => (failure = e) });\n await client.refresh();\n client.destroy();\n if (!client.getPayload()) throw failure ?? new Error(\"Could not load flags\");\n const user = client.forUser(attributes);\n const flag = str(\"flag\");\n const result = flag ? user.getDetails(flag, null) : user.getAll();\n out(`${JSON.stringify(result, null, 2)}\\n`);\n return 0;\n }\n throw new UsageError(`Unknown command: ${command}`);\n } catch (e) {\n if (e instanceof UsageError) {\n err(`${e.message}\\n\\n${HELP}`);\n return 2;\n }\n err(`guidely-flags: ${e instanceof Error ? e.message : String(e)}\\n`);\n return 1;\n }\n}\n","/** Node-only: finds flag keys used as string literals in a source tree (`guidely-flags code-refs`). */\nimport { execFileSync } from \"node:child_process\";\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface CodeRef {\n flag: string;\n path: string;\n line: number;\n snippet: string;\n}\n\nconst SKIP_DIRS = new Set([\"node_modules\", \".git\", \"dist\", \"build\", \"vendor\", \".next\", \".svelte-kit\", \".turbo\", \"coverage\", \"target\", \"out\"]);\nconst SKIP_FILES = /(^|\\/)(package-lock\\.json|pnpm-lock\\.yaml|yarn\\.lock|bun\\.lockb?|composer\\.lock|Cargo\\.lock|Gemfile\\.lock|poetry\\.lock)$|\\.(min\\.js|map|png|jpe?g|gif|webp|ico|svg|pdf|zip|gz|woff2?|ttf|eot|mp4|mov|mp3|jar|class|so|dylib|dll|exe|bin)$/i;\nexport const MAX_FILE_BYTES = 1024 * 1024;\nexport const MAX_REFS = 10_000;\n\n/** Files to scan, relative to `dir` with `/` separators: `git ls-files` (tracked + untracked, not ignored), else a directory walk. */\nexport function listFiles(dir: string): string[] {\n try {\n const out = execFileSync(\"git\", [\"ls-files\", \"-z\", \"--cached\", \"--others\", \"--exclude-standard\"], { cwd: dir, encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: 256 * 1024 * 1024 });\n const files = out.split(\"\\0\").filter(Boolean);\n if (files.length) return files.filter((f) => !f.split(\"/\").some((p) => SKIP_DIRS.has(p)));\n } catch {\n /* not a git checkout, or git missing */\n }\n const files: string[] = [];\n const walk = (abs: string) => {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n if (e.isDirectory()) {\n if (!SKIP_DIRS.has(e.name)) walk(join(abs, e.name));\n } else if (e.isFile()) files.push(relative(dir, join(abs, e.name)).split(sep).join(\"/\"));\n }\n };\n walk(dir);\n return files.sort();\n}\n\nconst escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\n/** Every line where a flag key appears as a quoted literal: `'key'`, `\"key\"` or `` `key` ``. */\nexport function scanFiles(dir: string, files: string[], flagKeys: string[]): CodeRef[] {\n const keys = Array.from(new Set(flagKeys.filter(Boolean))).sort((a, b) => b.length - a.length);\n if (!keys.length) return [];\n const re = new RegExp(`(['\"\\`])(${keys.map(escapeRe).join(\"|\")})\\\\1`, \"g\");\n const refs: CodeRef[] = [];\n for (const path of files) {\n if (SKIP_FILES.test(path)) continue;\n const abs = join(dir, path);\n let text: string;\n try {\n const st = statSync(abs);\n if (!st.isFile() || st.size > MAX_FILE_BYTES) continue;\n const buf = readFileSync(abs);\n if (buf.subarray(0, 8000).includes(0)) continue; // binary\n text = buf.toString(\"utf8\");\n } catch {\n continue;\n }\n if (!keys.some((k) => text.includes(k))) continue;\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n re.lastIndex = 0;\n const seen = new Set<string>();\n for (let m = re.exec(line); m; m = re.exec(line)) {\n const flag = m[2]!;\n if (seen.has(flag)) continue;\n seen.add(flag);\n refs.push({ flag, path, line: i + 1, snippet: line.trim().slice(0, 200) });\n if (refs.length >= MAX_REFS) return refs;\n }\n }\n }\n return refs;\n}\n\nexport function gitValue(dir: string, args: string[]): string | undefined {\n try {\n return execFileSync(\"git\", args, { cwd: dir, encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] }).trim() || undefined;\n } catch {\n return undefined;\n }\n}\n","#!/usr/bin/env node\nimport { main } from \"./cli-main\";\n\nmain(process.argv.slice(2)).then((code) => process.exit(code));\n"],"mappings":"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","names":["files"]}
|