@vercel/prepare-flags-definitions 0.2.0 → 0.3.0-1602289-20260528072704

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @vercel/prepare-flags-definitions
2
2
 
3
+ ## 0.3.0-1602289-20260528072704
4
+
5
+ ### Minor Changes
6
+
7
+ - [#390](https://github.com/vercel/flags/pull/390) [`503ac50`](https://github.com/vercel/flags/commit/503ac5077588b9d641b751f48df8575cb6993556) Thanks [@luismeyer](https://github.com/luismeyer)! - Add OIDC authentication support for Vercel Flags clients and generated flag definitions.
8
+
9
+ `@vercel/flags-core` can now create clients without an SDK key and authenticate with a Vercel OIDC token, while still supporting SDK keys and connection strings. Bundled definitions can be looked up by SDK key hash or OIDC project ID.
10
+
11
+ `@vercel/prepare-flags-definitions` now collects both SDK keys and `VERCEL_OIDC_TOKEN`, fetches definitions for each auth entry, deduplicates identical definitions across SDK keys and OIDC project IDs, and writes generated maps keyed by SDK key hash or project ID.
12
+
13
+ `@flags-sdk/vercel` now supports provider data lookup for Vercel flag origins that do not include an SDK key, allowing OIDC-backed clients to resolve project metadata.
14
+
15
+ ## 0.2.1
16
+
17
+ ### Patch Changes
18
+
19
+ - b755ffe: Fix SDK key detection to avoid false positives with third-party identifiers.
20
+
21
+ The SDK key validation now uses a regex to require the format `vf_server_*` or `vf_client_*` instead of accepting any string starting with `vf_`. This prevents false positives with third-party service identifiers that happen to start with `vf_` (e.g., Stripe identity flow IDs like `vf_1PyHgVLpWuMxVFx...`).
22
+
3
23
  ## 0.2.0
4
24
 
5
25
  ### Minor Changes
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # `@vercel/prepare-flags-definitions`
2
+
3
+ A build-time utility for [Vercel Flags](https://vercel.com/docs/flags/vercel-flags) that fetches flag definitions and bundles them into a synthetic `@vercel/flags-definitions` package inside `node_modules`. This allows `@vercel/flags-core` to access flag definitions instantly at runtime, even when the network is unavailable.
4
+
5
+ This package is used by the Vercel CLI and other build tools. You typically do not need to install it directly.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm i @vercel/prepare-flags-definitions
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { prepareFlagsDefinitions } from '@vercel/prepare-flags-definitions';
17
+
18
+ const result = await prepareFlagsDefinitions({
19
+ cwd: process.cwd(),
20
+ env: process.env,
21
+ userAgentSuffix: 'my-build-tool/1.0.0',
22
+ });
23
+
24
+ if (result.created) {
25
+ console.log(`Bundled definitions for ${result.entryCount} SDK keys`);
26
+ } else {
27
+ console.log(`No definitions created: ${result.reason}`);
28
+ }
29
+ ```
30
+
31
+ ## How It Works
32
+
33
+ 1. Scans environment variables for SDK keys (matching `vf_server_*` or `vf_client_*`)
34
+ 2. Fetches flag definitions from `flags.vercel.com` for each key
35
+ 3. Generates an optimized JavaScript module with deduplication and lazy parsing
36
+ 4. Writes the module to `node_modules/@vercel/flags-definitions/`
37
+
38
+ At runtime, `@vercel/flags-core` imports this module as a fallback when streaming or polling is unavailable.
39
+
40
+ ## Documentation
41
+
42
+ - [Embedded Definitions](https://vercel.com/docs/flags/vercel-flags/sdks/core#embedded-definitions)
43
+ - [Vercel Flags](https://vercel.com/docs/flags/vercel-flags)
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/index.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/index.ts
2
2
  var _crypto = require('crypto');
3
3
  var _promises = require('fs/promises');
4
4
  var _path = require('path');
5
5
 
6
6
  // package.json
7
- var version = "0.2.0";
7
+ var version = "0.3.0-1602289-20260528072704";
8
8
 
9
9
  // src/index.ts
10
10
  var FLAGS_HOST = "https://flags.vercel.com";
@@ -16,63 +16,105 @@ function obfuscate(sdkKey, prefixLength = 18) {
16
16
  function hashSdkKey(sdkKey) {
17
17
  return _crypto.createHash.call(void 0, "sha256").update(sdkKey).digest("hex");
18
18
  }
19
- function generateDefinitionsModule(sdkKeys, values) {
20
- const stringified = sdkKeys.map((_, i) => JSON.stringify(values[i]));
21
- const uniqueStrings = [];
22
- const stringToIndex = /* @__PURE__ */ new Map();
23
- for (const s of stringified) {
24
- if (!stringToIndex.has(s)) {
25
- stringToIndex.set(s, uniqueStrings.length);
26
- uniqueStrings.push(s);
27
- }
19
+ function getProjectIdFromOidcToken(oidcToken) {
20
+ const tokenParts = oidcToken.split(".");
21
+ if (tokenParts.length !== 3 || !tokenParts[1]) {
22
+ return;
28
23
  }
29
- const keyToIndex = sdkKeys.map(
30
- (_, i) => _nullishCoalesce(stringToIndex.get(stringified[i]), () => ( 0))
24
+ const payload = JSON.parse(
25
+ Buffer.from(tokenParts[1], "base64url").toString("utf8")
31
26
  );
32
- const hashedKeys = sdkKeys.map(hashSdkKey);
33
- const lines = [
34
- "const memo = (fn) => { let cached; return () => (cached ??= fn()); };",
35
- ""
36
- ];
37
- for (let i = 0; i < uniqueStrings.length; i++) {
38
- lines.push(
39
- `const _d${i} = memo(() => JSON.parse(${JSON.stringify(uniqueStrings[i])}));`
40
- );
27
+ if (typeof payload.project_id !== "string" || !payload.project_id) {
28
+ return;
41
29
  }
30
+ return payload.project_id;
31
+ }
32
+ function generateDefinitionConstants(lines, entries) {
33
+ const stringToConst = /* @__PURE__ */ new Map();
34
+ return entries.map((entry) => {
35
+ const stringified = JSON.stringify(entry.definitions);
36
+ let definitionConst = stringToConst.get(stringified);
37
+ if (!definitionConst) {
38
+ definitionConst = `_d${stringToConst.size}`;
39
+ stringToConst.set(stringified, definitionConst);
40
+ lines.push(
41
+ `const ${definitionConst} = memo(() => JSON.parse(${JSON.stringify(stringified)}));`
42
+ );
43
+ }
44
+ return { key: entry.key, value: definitionConst };
45
+ });
46
+ }
47
+ function generateMap(lines, entries) {
42
48
  lines.push("");
43
49
  lines.push("const map = {");
44
- for (let i = 0; i < sdkKeys.length; i++) {
45
- lines.push(` ${JSON.stringify(hashedKeys[i])}: _d${keyToIndex[i]},`);
50
+ for (const entry of entries) {
51
+ lines.push(` ${JSON.stringify(entry.key)}: ${entry.value},`);
46
52
  }
47
53
  lines.push("};");
48
54
  lines.push("");
49
- lines.push("export function get(hashedSdkKey) {");
50
- lines.push(" return map[hashedSdkKey]?.() ?? null;");
55
+ lines.push("export function get(key) {");
56
+ lines.push(" return map[key]?.() ?? null;");
51
57
  lines.push("}");
52
- lines.push("");
58
+ }
59
+ async function fetchDatafile(token, env, fetchFn, userAgentSuffix) {
60
+ const headers = {
61
+ authorization: `Bearer ${token}`,
62
+ "user-agent": [
63
+ `@vercel/prepare-flags-definitions/${version}`,
64
+ userAgentSuffix
65
+ ].filter(Boolean).join(" ")
66
+ };
67
+ if (env.VERCEL_PROJECT_ID) {
68
+ headers["x-vercel-project-id"] = env.VERCEL_PROJECT_ID;
69
+ }
70
+ if (env.VERCEL_ENV) {
71
+ headers["x-vercel-env"] = env.VERCEL_ENV;
72
+ }
73
+ if (env.VERCEL_DEPLOYMENT_ID) {
74
+ headers["x-vercel-deployment-id"] = env.VERCEL_DEPLOYMENT_ID;
75
+ }
76
+ if (env.VERCEL_REGION) {
77
+ headers["x-vercel-region"] = env.VERCEL_REGION;
78
+ }
79
+ const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });
80
+ if (!res.ok) {
81
+ if (res.status === 404) {
82
+ return void 0;
83
+ }
84
+ throw new Error(
85
+ `Failed to fetch flag definitions for ${obfuscate(token)}: ${res.status} ${res.statusText}`
86
+ );
87
+ }
88
+ return res.json();
89
+ }
90
+ function generateDefinitionsModule(entries, output) {
91
+ output == null ? void 0 : output.debug(
92
+ `vercel-flags: writing flag definitions for "${entries.map(({ key }) => obfuscate(key)).join(", ")}"`
93
+ );
94
+ const lines = [
95
+ "const memo = (fn) => { let cached; return () => (cached ??= fn()); };",
96
+ ""
97
+ ];
98
+ const generatedDefinitions = generateDefinitionConstants(lines, entries);
99
+ generateMap(lines, generatedDefinitions);
53
100
  lines.push(
101
+ "",
54
102
  `export const version = ${JSON.stringify(FLAGS_DEFINITIONS_VERSION)};`
55
103
  );
56
104
  return lines.join("\n");
57
105
  }
58
- async function prepareFlagsDefinitions(options) {
59
- const {
60
- cwd,
61
- env,
62
- userAgentSuffix,
63
- fetch: fetchFn = globalThis.fetch,
64
- output
65
- } = options;
66
- output == null ? void 0 : output.debug("vercel-flags: checking env vars for SDK Keys");
106
+ var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
107
+ function collectFlagEntries(env, output) {
108
+ const entries = [];
67
109
  const sdkKeys = Array.from(
68
110
  Object.values(env).reduce((acc, value) => {
69
111
  if (typeof value === "string") {
70
- if (value.startsWith("vf_")) {
112
+ if (SDK_KEY_REGEX.test(value)) {
71
113
  acc.add(value);
72
114
  } else if (value.startsWith("flags:")) {
73
115
  const params = new URLSearchParams(value.slice("flags:".length));
74
116
  const sdkKey = params.get("sdkKey");
75
- if (sdkKey == null ? void 0 : sdkKey.startsWith("vf_")) {
117
+ if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) {
76
118
  acc.add(sdkKey);
77
119
  }
78
120
  }
@@ -80,48 +122,68 @@ async function prepareFlagsDefinitions(options) {
80
122
  return acc;
81
123
  }, /* @__PURE__ */ new Set())
82
124
  );
83
- output == null ? void 0 : output.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);
84
- if (sdkKeys.length === 0) {
85
- return { created: false, reason: "no-sdk-keys" };
125
+ if (sdkKeys.length > 0) {
126
+ output == null ? void 0 : output.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);
127
+ for (const key of sdkKeys) {
128
+ entries.push({ type: "sdkKey", key });
129
+ }
86
130
  }
87
- const fetchPromise = Promise.all(
88
- sdkKeys.map(async (sdkKey) => {
89
- const headers = {
90
- authorization: `Bearer ${sdkKey}`,
91
- "user-agent": [
92
- `@vercel/prepare-flags-definitions/${version}`,
93
- userAgentSuffix
94
- ].filter(Boolean).join(" ")
95
- };
96
- if (env.VERCEL_PROJECT_ID) {
97
- headers["x-vercel-project-id"] = env.VERCEL_PROJECT_ID;
98
- }
99
- if (env.VERCEL_ENV) {
100
- headers["x-vercel-env"] = env.VERCEL_ENV;
101
- }
102
- if (env.VERCEL_DEPLOYMENT_ID) {
103
- headers["x-vercel-deployment-id"] = env.VERCEL_DEPLOYMENT_ID;
131
+ const oidcToken = env.VERCEL_OIDC_TOKEN;
132
+ if (oidcToken && (oidcToken == null ? void 0 : oidcToken.length) > 0) {
133
+ output == null ? void 0 : output.debug(`vercel-flags: found OIDC token`);
134
+ entries.push({ type: "oidcToken", key: oidcToken });
135
+ }
136
+ return entries;
137
+ }
138
+ async function prepareFlagsDefinitions(options) {
139
+ const {
140
+ cwd,
141
+ env,
142
+ userAgentSuffix,
143
+ fetch: fetchFn = globalThis.fetch,
144
+ output
145
+ } = options;
146
+ output == null ? void 0 : output.debug("vercel-flags: checking env vars for SDK Keys and OIDC Token");
147
+ const entries = collectFlagEntries(env, output);
148
+ if (entries.length === 0) {
149
+ return { created: false, reason: "no-flags-entries" };
150
+ }
151
+ const resolvedEntries = await Promise.all(
152
+ entries.map(async ({ key, type }) => {
153
+ const definitions = await fetchDatafile(
154
+ key,
155
+ env,
156
+ fetchFn,
157
+ userAgentSuffix
158
+ );
159
+ if (!definitions) {
160
+ return;
104
161
  }
105
- if (env.VERCEL_REGION) {
106
- headers["x-vercel-region"] = env.VERCEL_REGION;
162
+ if (type === "oidcToken") {
163
+ const projectId = getProjectIdFromOidcToken(key);
164
+ if (projectId) {
165
+ return {
166
+ key: projectId,
167
+ definitions
168
+ };
169
+ }
107
170
  }
108
- const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });
109
- if (!res.ok) {
110
- throw new Error(
111
- `Failed to fetch flag definitions for ${obfuscate(sdkKey)}: ${res.status} ${res.statusText}`
112
- );
171
+ if (type === "sdkKey") {
172
+ return {
173
+ key: hashSdkKey(key),
174
+ definitions
175
+ };
113
176
  }
114
- return res.json();
115
177
  })
116
178
  );
117
- const values = output ? await output.time("vercel-flags: load datafiles", fetchPromise) : await fetchPromise;
118
- const definitionsJs = generateDefinitionsModule(sdkKeys, values);
179
+ const validEntries = resolvedEntries.filter((entry) => !!entry);
180
+ const definitionsJs = generateDefinitionsModule(validEntries, output);
119
181
  const storageDir = _path.join.call(void 0, cwd, "node_modules", "@vercel", "flags-definitions");
120
182
  const indexPath = _path.join.call(void 0, storageDir, "index.js");
121
183
  const dtsPath = _path.join.call(void 0, storageDir, "index.d.ts");
122
184
  const packageJsonPath = _path.join.call(void 0, storageDir, "package.json");
123
185
  const dts = [
124
- "export function get(hashedSdkKey: string): Record<string, unknown> | null;",
186
+ "export function get(key: string): Record<string, unknown> | null;",
125
187
  "export const version: string;",
126
188
  ""
127
189
  ].join("\n");
@@ -148,14 +210,12 @@ async function prepareFlagsDefinitions(options) {
148
210
  output == null ? void 0 : output.debug(` \u2192 ${indexPath}`);
149
211
  output == null ? void 0 : output.debug(` \u2192 ${dtsPath}`);
150
212
  output == null ? void 0 : output.debug(` \u2192 ${packageJsonPath}`);
151
- output == null ? void 0 : output.debug(
152
- ` \u2192 included definitions for keys "${sdkKeys.map((key) => obfuscate(key)).join(", ")}"`
153
- );
154
- return { created: true, sdkKeysCount: sdkKeys.length };
213
+ return { created: true, entryCount: entries.length };
155
214
  }
156
215
 
157
216
 
158
217
 
159
218
 
160
- exports.generateDefinitionsModule = generateDefinitionsModule; exports.hashSdkKey = hashSdkKey; exports.prepareFlagsDefinitions = prepareFlagsDefinitions;
219
+
220
+ exports.generateDefinitionsModule = generateDefinitionsModule; exports.getProjectIdFromOidcToken = getProjectIdFromOidcToken; exports.hashSdkKey = hashSdkKey; exports.prepareFlagsDefinitions = prepareFlagsDefinitions;
161
221
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/flags/flags/packages/prepare-flags-definitions/dist/index.cjs","../src/index.ts","../package.json"],"names":[],"mappings":"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","file":"/home/runner/work/flags/flags/packages/prepare-flags-definitions/dist/index.cjs","sourcesContent":[null,"import { createHash } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { version as PACKAGE_VERSION } from '../package.json';\n\nconst FLAGS_HOST = 'https://flags.vercel.com';\nconst FLAGS_DEFINITIONS_VERSION = '1.0.1';\n\ntype BundledDefinitions = Record<string, unknown>;\n\nexport interface Output {\n debug(message: string): void;\n time<T>(label: string, promise: Promise<T>): Promise<T>;\n}\n\nexport type PrepareFlagsDefinitionsResult =\n | { created: false; reason: 'no-sdk-keys' }\n | { created: true; sdkKeysCount: number };\n\n/**\n * Obfuscates SDK key for logging (shows first 18 chars)\n */\nfunction obfuscate(sdkKey: string, prefixLength = 18): string {\n if (prefixLength >= sdkKey.length) return sdkKey;\n return (\n sdkKey.slice(0, prefixLength) + '*'.repeat(sdkKey.length - prefixLength)\n );\n}\n\n/**\n * Computes a SHA-256 hex digest of the given SDK key.\n */\nexport function hashSdkKey(sdkKey: string): string {\n return createHash('sha256').update(sdkKey).digest('hex');\n}\n\n/**\n * Generates a JS module with deduplicated, lazily-parsed definitions.\n *\n * The map keys are SHA-256 hashes of the SDK keys so that raw keys\n * are not embedded in the output.\n *\n * Output format:\n * ```js\n * const memo = (fn) => { let cached; return () => (cached ??= fn()); };\n * const _d0 = memo(() => JSON.parse('...'));\n * const map = { \"<sha256_hash>\": _d0 };\n * export function get(hashedSdkKey) { return map[hashedSdkKey]?.() ?? null; }\n * ```\n */\nexport function generateDefinitionsModule(\n sdkKeys: string[],\n values: BundledDefinitions[],\n): string {\n // Stringify each definition\n const stringified = sdkKeys.map((_, i) => JSON.stringify(values[i]));\n\n // Deduplicate: map unique strings to indices\n const uniqueStrings: string[] = [];\n const stringToIndex = new Map<string, number>();\n for (const s of stringified) {\n if (!stringToIndex.has(s)) {\n stringToIndex.set(s, uniqueStrings.length);\n uniqueStrings.push(s);\n }\n }\n\n // Map SDK keys to their definition index\n const keyToIndex = sdkKeys.map(\n (_, i) => stringToIndex.get(stringified[i]!) ?? 0,\n );\n\n // Hash each SDK key\n const hashedKeys = sdkKeys.map(hashSdkKey);\n\n // Generate JS\n const lines: string[] = [\n 'const memo = (fn) => { let cached; return () => (cached ??= fn()); };',\n '',\n ];\n\n // Add definition constants\n for (let i = 0; i < uniqueStrings.length; i++) {\n lines.push(\n `const _d${i} = memo(() => JSON.parse(${JSON.stringify(uniqueStrings[i])}));`,\n );\n }\n\n lines.push('');\n lines.push('const map = {');\n for (let i = 0; i < sdkKeys.length; i++) {\n lines.push(` ${JSON.stringify(hashedKeys[i])}: _d${keyToIndex[i]},`);\n }\n lines.push('};');\n lines.push('');\n lines.push('export function get(hashedSdkKey) {');\n lines.push(' return map[hashedSdkKey]?.() ?? null;');\n lines.push('}');\n lines.push('');\n lines.push(\n `export const version = ${JSON.stringify(FLAGS_DEFINITIONS_VERSION)};`,\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Prepares flag definitions by reading SDK keys from environment variables,\n * fetching definitions from flags.vercel.com, and writing them into a\n * synthetic `@vercel/flags-definitions` package inside `node_modules/`.\n */\nexport async function prepareFlagsDefinitions(options: {\n cwd: string;\n env: Record<string, string | undefined>;\n /**\n * Appended to the user-agent header to identify the caller.\n * Example: `\"vercel-cli/35.0.0\"`\n */\n userAgentSuffix?: string;\n fetch?: typeof globalThis.fetch;\n output?: Output;\n}): Promise<PrepareFlagsDefinitionsResult> {\n const {\n cwd,\n env,\n userAgentSuffix,\n fetch: fetchFn = globalThis.fetch,\n output,\n } = options;\n\n output?.debug('vercel-flags: checking env vars for SDK Keys');\n\n // Collect unique SDK keys from environment variables\n // Supports both direct SDK keys (vf_ prefix) and flags: format\n const sdkKeys = Array.from(\n Object.values(env).reduce<Set<string>>((acc, value) => {\n if (typeof value === 'string') {\n if (value.startsWith('vf_')) {\n acc.add(value);\n } else if (value.startsWith('flags:')) {\n const params = new URLSearchParams(value.slice('flags:'.length));\n const sdkKey = params.get('sdkKey');\n if (sdkKey?.startsWith('vf_')) {\n acc.add(sdkKey);\n }\n }\n }\n return acc;\n }, new Set<string>()),\n );\n\n output?.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);\n\n if (sdkKeys.length === 0) {\n return { created: false, reason: 'no-sdk-keys' };\n }\n\n // Fetch definitions for each SDK key\n const fetchPromise = Promise.all(\n sdkKeys.map(async (sdkKey) => {\n const headers: Record<string, string> = {\n authorization: `Bearer ${sdkKey}`,\n 'user-agent': [\n `@vercel/prepare-flags-definitions/${PACKAGE_VERSION}`,\n userAgentSuffix,\n ]\n .filter(Boolean)\n .join(' '),\n };\n\n // Add Vercel metadata headers if available\n if (env.VERCEL_PROJECT_ID) {\n headers['x-vercel-project-id'] = env.VERCEL_PROJECT_ID;\n }\n if (env.VERCEL_ENV) {\n headers['x-vercel-env'] = env.VERCEL_ENV;\n }\n if (env.VERCEL_DEPLOYMENT_ID) {\n headers['x-vercel-deployment-id'] = env.VERCEL_DEPLOYMENT_ID;\n }\n if (env.VERCEL_REGION) {\n headers['x-vercel-region'] = env.VERCEL_REGION;\n }\n\n const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });\n\n if (!res.ok) {\n throw new Error(\n `Failed to fetch flag definitions for ${obfuscate(sdkKey)}: ${res.status} ${res.statusText}`,\n );\n }\n\n return res.json() as Promise<BundledDefinitions>;\n }),\n );\n\n const values = output\n ? await output.time('vercel-flags: load datafiles', fetchPromise)\n : await fetchPromise;\n\n // Generate the JS module\n const definitionsJs = generateDefinitionsModule(sdkKeys, values);\n\n // Write to node_modules/@vercel/flags-definitions/\n const storageDir = join(cwd, 'node_modules', '@vercel', 'flags-definitions');\n const indexPath = join(storageDir, 'index.js');\n const dtsPath = join(storageDir, 'index.d.ts');\n const packageJsonPath = join(storageDir, 'package.json');\n\n const dts = [\n 'export function get(hashedSdkKey: string): Record<string, unknown> | null;',\n 'export const version: string;',\n '',\n ].join('\\n');\n\n const packageJson = {\n name: '@vercel/flags-definitions',\n version: FLAGS_DEFINITIONS_VERSION,\n type: 'module',\n main: './index.js',\n types: './index.d.ts',\n exports: {\n '.': {\n types: './index.d.ts',\n import: './index.js',\n },\n },\n };\n\n await mkdir(storageDir, { recursive: true });\n await Promise.all([\n writeFile(indexPath, definitionsJs),\n writeFile(dtsPath, dts),\n writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)),\n ]);\n\n output?.debug('vercel-flags: created module');\n output?.debug(` → ${indexPath}`);\n output?.debug(` → ${dtsPath}`);\n output?.debug(` → ${packageJsonPath}`);\n output?.debug(\n ` → included definitions for keys \"${sdkKeys.map((key) => obfuscate(key)).join(', ')}\"`,\n );\n\n return { created: true, sdkKeysCount: sdkKeys.length };\n}\n","{\n \"name\": \"@vercel/prepare-flags-definitions\",\n \"version\": \"0.2.0\",\n \"description\": \"\",\n \"keywords\": [],\n \"license\": \"MIT\",\n \"author\": \"\",\n \"sideEffects\": false,\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \".\": [\n \"dist/*.d.ts\",\n \"dist/*.d.cts\"\n ]\n }\n },\n \"files\": [\n \"dist\",\n \"CHANGELOG.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"check\": \"biome check\",\n \"test\": \"vitest --run\",\n \"test:watch\": \"vitest\",\n \"type-check\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@types/node\": \"20.11.17\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"5.6.3\",\n \"vite\": \"6.4.1\",\n \"vitest\": \"2.1.9\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/flags/flags/packages/prepare-flags-definitions/dist/index.cjs","../src/index.ts","../package.json"],"names":[],"mappings":"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","file":"/home/runner/work/flags/flags/packages/prepare-flags-definitions/dist/index.cjs","sourcesContent":[null,"import { createHash } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { version as PACKAGE_VERSION } from '../package.json';\n\nconst FLAGS_HOST = 'https://flags.vercel.com';\nconst FLAGS_DEFINITIONS_VERSION = '1.0.1';\n\ntype BundledDefinitions = Record<string, unknown>;\n\nexport interface Output {\n debug(message: string): void;\n time<T>(label: string, promise: Promise<T>): Promise<T>;\n}\n\nexport type PrepareFlagsDefinitionsResult =\n | { created: false; reason: 'no-flags-entries' }\n | { created: true; entryCount: number };\n\n/**\n * Obfuscates SDK key for logging (shows first 18 chars)\n */\nfunction obfuscate(sdkKey: string, prefixLength = 18): string {\n if (prefixLength >= sdkKey.length) return sdkKey;\n return (\n sdkKey.slice(0, prefixLength) + '*'.repeat(sdkKey.length - prefixLength)\n );\n}\n\n/**\n * Computes a SHA-256 hex digest of the given SDK key.\n */\nexport function hashSdkKey(sdkKey: string): string {\n return createHash('sha256').update(sdkKey).digest('hex');\n}\n\nexport function getProjectIdFromOidcToken(oidcToken: string) {\n const tokenParts = oidcToken.split('.');\n if (tokenParts.length !== 3 || !tokenParts[1]) {\n return;\n }\n\n const payload = JSON.parse(\n Buffer.from(tokenParts[1], 'base64url').toString('utf8'),\n ) as { project_id?: unknown };\n\n if (typeof payload.project_id !== 'string' || !payload.project_id) {\n return;\n }\n\n return payload.project_id;\n}\n\ntype DefinitionsEntry = {\n key: string;\n definitions: BundledDefinitions;\n};\n\ntype MapEntry = {\n key: string;\n value: string;\n};\n\n/**\n * Creates js constants pointing to memoized deduplicated flag definitions.\n * Output format:\n * ```js\n * const _d0 = memo(() => JSON.parse('...'));\n * const _d1 = memo(() => JSON.parse('...'));\n * ````\n */\nfunction generateDefinitionConstants(\n lines: string[],\n entries: DefinitionsEntry[],\n): MapEntry[] {\n const stringToConst = new Map<string, string>();\n\n return entries.map((entry) => {\n const stringified = JSON.stringify(entry.definitions);\n let definitionConst = stringToConst.get(stringified);\n\n if (!definitionConst) {\n definitionConst = `_d${stringToConst.size}`;\n stringToConst.set(stringified, definitionConst);\n lines.push(\n `const ${definitionConst} = memo(() => JSON.parse(${JSON.stringify(stringified)}));`,\n );\n }\n\n return { key: entry.key, value: definitionConst };\n });\n}\n\n/**\n * Creates a js map and getter function for exposing key value pairs.\n * Output format:\n * ```js\n * const map = { \"<sha256_hash_or_project_id>\": _d0 };\n * export function get(key) { return map[key]?.() ?? null; }\n * ````\n */\nfunction generateMap(lines: string[], entries: MapEntry[]): void {\n lines.push('');\n lines.push('const map = {');\n for (const entry of entries) {\n lines.push(` ${JSON.stringify(entry.key)}: ${entry.value},`);\n }\n lines.push('};');\n lines.push('');\n lines.push('export function get(key) {');\n lines.push(' return map[key]?.() ?? null;');\n lines.push('}');\n}\n\nasync function fetchDatafile(\n token: string,\n env: Record<string, string | undefined>,\n fetchFn: typeof globalThis.fetch,\n userAgentSuffix?: string,\n): Promise<BundledDefinitions | undefined> {\n const headers: Record<string, string> = {\n authorization: `Bearer ${token}`,\n 'user-agent': [\n `@vercel/prepare-flags-definitions/${PACKAGE_VERSION}`,\n userAgentSuffix,\n ]\n .filter(Boolean)\n .join(' '),\n };\n\n // Add Vercel metadata headers if available\n if (env.VERCEL_PROJECT_ID) {\n headers['x-vercel-project-id'] = env.VERCEL_PROJECT_ID;\n }\n if (env.VERCEL_ENV) {\n headers['x-vercel-env'] = env.VERCEL_ENV;\n }\n if (env.VERCEL_DEPLOYMENT_ID) {\n headers['x-vercel-deployment-id'] = env.VERCEL_DEPLOYMENT_ID;\n }\n if (env.VERCEL_REGION) {\n headers['x-vercel-region'] = env.VERCEL_REGION;\n }\n\n const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });\n\n if (!res.ok) {\n if (res.status === 404) {\n return undefined;\n }\n\n throw new Error(\n `Failed to fetch flag definitions for ${obfuscate(token)}: ${res.status} ${res.statusText}`,\n );\n }\n\n return res.json() as Promise<BundledDefinitions>;\n}\n\n/**\n * Generates a JS module with deduplicated, lazily-parsed definitions.\n *\n * The map keys are SHA-256 hashes of the SDK keys so that raw keys\n * are not embedded in the output.\n *\n * Output format:\n * ```js\n * const memo = (fn) => { let cached; return () => (cached ??= fn()); };\n * const _d0 = memo(() => JSON.parse('...'));\n * const map = { \"<sha256_hash>\": _d0 };\n * export function get(hashedSdkKey) { return map[hashedSdkKey]?.() ?? null; }\n * ```\n */\nexport function generateDefinitionsModule(\n entries: DefinitionsEntry[],\n output: Output | undefined,\n): string {\n output?.debug(\n `vercel-flags: writing flag definitions for \"${entries.map(({ key }) => obfuscate(key)).join(', ')}\"`,\n );\n\n // generate shared js\n const lines: string[] = [\n 'const memo = (fn) => { let cached; return () => (cached ??= fn()); };',\n '',\n ];\n\n // generate js const and capture the const names\n const generatedDefinitions = generateDefinitionConstants(lines, entries);\n\n // generate a map wiring keys to const names\n generateMap(lines, generatedDefinitions);\n\n lines.push(\n '',\n `export const version = ${JSON.stringify(FLAGS_DEFINITIONS_VERSION)};`,\n );\n\n return lines.join('\\n');\n}\n\ntype FlagEntry = {\n type: 'oidcToken' | 'sdkKey';\n key: string;\n};\n\n/**\n * Regex to match valid Vercel Flags SDK keys.\n * SDK keys must follow the format: vf_server_* or vf_client_*\n * This avoids false positives with third-party identifiers that happen\n * to start with 'vf_' (e.g., Stripe identity flow IDs like 'vf_1PyH...').\n */\nconst SDK_KEY_REGEX = /^vf_(?:server|client)_/;\n\n/**\n * Collect all possible flag entries the need embedding from the environment\n */\nfunction collectFlagEntries(\n env: Record<string, string | undefined>,\n output: Output | undefined,\n): FlagEntry[] {\n const entries: FlagEntry[] = [];\n\n // Collect unique SDK keys from environment variables\n // Supports both direct SDK keys (vf_server_*/vf_client_*) and flags: format\n const sdkKeys = Array.from(\n Object.values(env).reduce<Set<string>>((acc, value) => {\n if (typeof value === 'string') {\n if (SDK_KEY_REGEX.test(value)) {\n acc.add(value);\n } else if (value.startsWith('flags:')) {\n const params = new URLSearchParams(value.slice('flags:'.length));\n const sdkKey = params.get('sdkKey');\n if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) {\n acc.add(sdkKey);\n }\n }\n }\n return acc;\n }, new Set<string>()),\n );\n\n if (sdkKeys.length > 0) {\n output?.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);\n\n for (const key of sdkKeys) {\n entries.push({ type: 'sdkKey', key });\n }\n }\n\n const oidcToken = env.VERCEL_OIDC_TOKEN;\n if (oidcToken && oidcToken?.length > 0) {\n output?.debug(`vercel-flags: found OIDC token`);\n\n entries.push({ type: 'oidcToken', key: oidcToken });\n }\n\n return entries;\n}\n\n/**\n * Prepares flag definitions by reading SDK keys from environment variables,\n * fetching definitions from flags.vercel.com, and writing them into a\n * synthetic `@vercel/flags-definitions` package inside `node_modules/`.\n */\nexport async function prepareFlagsDefinitions(options: {\n cwd: string;\n env: Record<string, string | undefined>;\n /**\n * Appended to the user-agent header to identify the caller.\n * Example: `\"vercel-cli/35.0.0\"`\n */\n userAgentSuffix?: string;\n fetch?: typeof globalThis.fetch;\n output?: Output;\n}): Promise<PrepareFlagsDefinitionsResult> {\n const {\n cwd,\n env,\n userAgentSuffix,\n fetch: fetchFn = globalThis.fetch,\n output,\n } = options;\n\n output?.debug('vercel-flags: checking env vars for SDK Keys and OIDC Token');\n\n const entries = collectFlagEntries(env, output);\n if (entries.length === 0) {\n return { created: false, reason: 'no-flags-entries' };\n }\n\n // fetch all datafiles for sdk keys and oidc tokens\n const resolvedEntries = await Promise.all(\n entries.map(async ({ key, type }) => {\n const definitions = await fetchDatafile(\n key,\n env,\n fetchFn,\n userAgentSuffix,\n );\n if (!definitions) {\n return;\n }\n\n if (type === 'oidcToken') {\n const projectId = getProjectIdFromOidcToken(key);\n\n if (projectId) {\n return {\n key: projectId,\n definitions,\n };\n }\n }\n\n if (type === 'sdkKey') {\n return {\n key: hashSdkKey(key),\n definitions,\n };\n }\n }),\n );\n\n const validEntries = resolvedEntries.filter((entry) => !!entry);\n\n const definitionsJs = generateDefinitionsModule(validEntries, output);\n\n // Write to node_modules/@vercel/flags-definitions/\n const storageDir = join(cwd, 'node_modules', '@vercel', 'flags-definitions');\n const indexPath = join(storageDir, 'index.js');\n const dtsPath = join(storageDir, 'index.d.ts');\n const packageJsonPath = join(storageDir, 'package.json');\n\n const dts = [\n 'export function get(key: string): Record<string, unknown> | null;',\n 'export const version: string;',\n '',\n ].join('\\n');\n\n const packageJson = {\n name: '@vercel/flags-definitions',\n version: FLAGS_DEFINITIONS_VERSION,\n type: 'module',\n main: './index.js',\n types: './index.d.ts',\n exports: {\n '.': {\n types: './index.d.ts',\n import: './index.js',\n },\n },\n };\n\n await mkdir(storageDir, { recursive: true });\n await Promise.all([\n writeFile(indexPath, definitionsJs),\n writeFile(dtsPath, dts),\n writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)),\n ]);\n\n output?.debug('vercel-flags: created module');\n output?.debug(` → ${indexPath}`);\n output?.debug(` → ${dtsPath}`);\n output?.debug(` → ${packageJsonPath}`);\n\n return { created: true, entryCount: entries.length };\n}\n","{\n \"name\": \"@vercel/prepare-flags-definitions\",\n \"version\": \"0.3.0-1602289-20260528072704\",\n \"description\": \"A utility for preparing flags definitions for embedding\",\n \"keywords\": [\n \"flags\",\n \"flags sdk\",\n \"experimentation\",\n \"ab testing\"\n ],\n \"license\": \"MIT\",\n \"author\": \"Dominik Ferber <dominik@vercel.com>\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/vercel/flags.git\"\n },\n \"sideEffects\": false,\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \".\": [\n \"dist/*.d.ts\",\n \"dist/*.d.cts\"\n ]\n }\n },\n \"files\": [\n \"dist\",\n \"CHANGELOG.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"check\": \"biome check\",\n \"test\": \"vitest --run\",\n \"test:watch\": \"vitest\",\n \"type-check\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@types/node\": \"20.11.17\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"5.6.3\",\n \"vite\": \"6.4.1\",\n \"vitest\": \"2.1.9\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -5,15 +5,20 @@ interface Output {
5
5
  }
6
6
  type PrepareFlagsDefinitionsResult = {
7
7
  created: false;
8
- reason: 'no-sdk-keys';
8
+ reason: 'no-flags-entries';
9
9
  } | {
10
10
  created: true;
11
- sdkKeysCount: number;
11
+ entryCount: number;
12
12
  };
13
13
  /**
14
14
  * Computes a SHA-256 hex digest of the given SDK key.
15
15
  */
16
16
  declare function hashSdkKey(sdkKey: string): string;
17
+ declare function getProjectIdFromOidcToken(oidcToken: string): string | undefined;
18
+ type DefinitionsEntry = {
19
+ key: string;
20
+ definitions: BundledDefinitions;
21
+ };
17
22
  /**
18
23
  * Generates a JS module with deduplicated, lazily-parsed definitions.
19
24
  *
@@ -28,7 +33,7 @@ declare function hashSdkKey(sdkKey: string): string;
28
33
  * export function get(hashedSdkKey) { return map[hashedSdkKey]?.() ?? null; }
29
34
  * ```
30
35
  */
31
- declare function generateDefinitionsModule(sdkKeys: string[], values: BundledDefinitions[]): string;
36
+ declare function generateDefinitionsModule(entries: DefinitionsEntry[], output: Output | undefined): string;
32
37
  /**
33
38
  * Prepares flag definitions by reading SDK keys from environment variables,
34
39
  * fetching definitions from flags.vercel.com, and writing them into a
@@ -46,4 +51,4 @@ declare function prepareFlagsDefinitions(options: {
46
51
  output?: Output;
47
52
  }): Promise<PrepareFlagsDefinitionsResult>;
48
53
 
49
- export { type Output, type PrepareFlagsDefinitionsResult, generateDefinitionsModule, hashSdkKey, prepareFlagsDefinitions };
54
+ export { type Output, type PrepareFlagsDefinitionsResult, generateDefinitionsModule, getProjectIdFromOidcToken, hashSdkKey, prepareFlagsDefinitions };
package/dist/index.d.ts CHANGED
@@ -5,15 +5,20 @@ interface Output {
5
5
  }
6
6
  type PrepareFlagsDefinitionsResult = {
7
7
  created: false;
8
- reason: 'no-sdk-keys';
8
+ reason: 'no-flags-entries';
9
9
  } | {
10
10
  created: true;
11
- sdkKeysCount: number;
11
+ entryCount: number;
12
12
  };
13
13
  /**
14
14
  * Computes a SHA-256 hex digest of the given SDK key.
15
15
  */
16
16
  declare function hashSdkKey(sdkKey: string): string;
17
+ declare function getProjectIdFromOidcToken(oidcToken: string): string | undefined;
18
+ type DefinitionsEntry = {
19
+ key: string;
20
+ definitions: BundledDefinitions;
21
+ };
17
22
  /**
18
23
  * Generates a JS module with deduplicated, lazily-parsed definitions.
19
24
  *
@@ -28,7 +33,7 @@ declare function hashSdkKey(sdkKey: string): string;
28
33
  * export function get(hashedSdkKey) { return map[hashedSdkKey]?.() ?? null; }
29
34
  * ```
30
35
  */
31
- declare function generateDefinitionsModule(sdkKeys: string[], values: BundledDefinitions[]): string;
36
+ declare function generateDefinitionsModule(entries: DefinitionsEntry[], output: Output | undefined): string;
32
37
  /**
33
38
  * Prepares flag definitions by reading SDK keys from environment variables,
34
39
  * fetching definitions from flags.vercel.com, and writing them into a
@@ -46,4 +51,4 @@ declare function prepareFlagsDefinitions(options: {
46
51
  output?: Output;
47
52
  }): Promise<PrepareFlagsDefinitionsResult>;
48
53
 
49
- export { type Output, type PrepareFlagsDefinitionsResult, generateDefinitionsModule, hashSdkKey, prepareFlagsDefinitions };
54
+ export { type Output, type PrepareFlagsDefinitionsResult, generateDefinitionsModule, getProjectIdFromOidcToken, hashSdkKey, prepareFlagsDefinitions };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { mkdir, writeFile } from "fs/promises";
4
4
  import { join } from "path";
5
5
 
6
6
  // package.json
7
- var version = "0.2.0";
7
+ var version = "0.3.0-1602289-20260528072704";
8
8
 
9
9
  // src/index.ts
10
10
  var FLAGS_HOST = "https://flags.vercel.com";
@@ -16,63 +16,105 @@ function obfuscate(sdkKey, prefixLength = 18) {
16
16
  function hashSdkKey(sdkKey) {
17
17
  return createHash("sha256").update(sdkKey).digest("hex");
18
18
  }
19
- function generateDefinitionsModule(sdkKeys, values) {
20
- const stringified = sdkKeys.map((_, i) => JSON.stringify(values[i]));
21
- const uniqueStrings = [];
22
- const stringToIndex = /* @__PURE__ */ new Map();
23
- for (const s of stringified) {
24
- if (!stringToIndex.has(s)) {
25
- stringToIndex.set(s, uniqueStrings.length);
26
- uniqueStrings.push(s);
27
- }
19
+ function getProjectIdFromOidcToken(oidcToken) {
20
+ const tokenParts = oidcToken.split(".");
21
+ if (tokenParts.length !== 3 || !tokenParts[1]) {
22
+ return;
28
23
  }
29
- const keyToIndex = sdkKeys.map(
30
- (_, i) => stringToIndex.get(stringified[i]) ?? 0
24
+ const payload = JSON.parse(
25
+ Buffer.from(tokenParts[1], "base64url").toString("utf8")
31
26
  );
32
- const hashedKeys = sdkKeys.map(hashSdkKey);
33
- const lines = [
34
- "const memo = (fn) => { let cached; return () => (cached ??= fn()); };",
35
- ""
36
- ];
37
- for (let i = 0; i < uniqueStrings.length; i++) {
38
- lines.push(
39
- `const _d${i} = memo(() => JSON.parse(${JSON.stringify(uniqueStrings[i])}));`
40
- );
27
+ if (typeof payload.project_id !== "string" || !payload.project_id) {
28
+ return;
41
29
  }
30
+ return payload.project_id;
31
+ }
32
+ function generateDefinitionConstants(lines, entries) {
33
+ const stringToConst = /* @__PURE__ */ new Map();
34
+ return entries.map((entry) => {
35
+ const stringified = JSON.stringify(entry.definitions);
36
+ let definitionConst = stringToConst.get(stringified);
37
+ if (!definitionConst) {
38
+ definitionConst = `_d${stringToConst.size}`;
39
+ stringToConst.set(stringified, definitionConst);
40
+ lines.push(
41
+ `const ${definitionConst} = memo(() => JSON.parse(${JSON.stringify(stringified)}));`
42
+ );
43
+ }
44
+ return { key: entry.key, value: definitionConst };
45
+ });
46
+ }
47
+ function generateMap(lines, entries) {
42
48
  lines.push("");
43
49
  lines.push("const map = {");
44
- for (let i = 0; i < sdkKeys.length; i++) {
45
- lines.push(` ${JSON.stringify(hashedKeys[i])}: _d${keyToIndex[i]},`);
50
+ for (const entry of entries) {
51
+ lines.push(` ${JSON.stringify(entry.key)}: ${entry.value},`);
46
52
  }
47
53
  lines.push("};");
48
54
  lines.push("");
49
- lines.push("export function get(hashedSdkKey) {");
50
- lines.push(" return map[hashedSdkKey]?.() ?? null;");
55
+ lines.push("export function get(key) {");
56
+ lines.push(" return map[key]?.() ?? null;");
51
57
  lines.push("}");
52
- lines.push("");
58
+ }
59
+ async function fetchDatafile(token, env, fetchFn, userAgentSuffix) {
60
+ const headers = {
61
+ authorization: `Bearer ${token}`,
62
+ "user-agent": [
63
+ `@vercel/prepare-flags-definitions/${version}`,
64
+ userAgentSuffix
65
+ ].filter(Boolean).join(" ")
66
+ };
67
+ if (env.VERCEL_PROJECT_ID) {
68
+ headers["x-vercel-project-id"] = env.VERCEL_PROJECT_ID;
69
+ }
70
+ if (env.VERCEL_ENV) {
71
+ headers["x-vercel-env"] = env.VERCEL_ENV;
72
+ }
73
+ if (env.VERCEL_DEPLOYMENT_ID) {
74
+ headers["x-vercel-deployment-id"] = env.VERCEL_DEPLOYMENT_ID;
75
+ }
76
+ if (env.VERCEL_REGION) {
77
+ headers["x-vercel-region"] = env.VERCEL_REGION;
78
+ }
79
+ const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });
80
+ if (!res.ok) {
81
+ if (res.status === 404) {
82
+ return void 0;
83
+ }
84
+ throw new Error(
85
+ `Failed to fetch flag definitions for ${obfuscate(token)}: ${res.status} ${res.statusText}`
86
+ );
87
+ }
88
+ return res.json();
89
+ }
90
+ function generateDefinitionsModule(entries, output) {
91
+ output == null ? void 0 : output.debug(
92
+ `vercel-flags: writing flag definitions for "${entries.map(({ key }) => obfuscate(key)).join(", ")}"`
93
+ );
94
+ const lines = [
95
+ "const memo = (fn) => { let cached; return () => (cached ??= fn()); };",
96
+ ""
97
+ ];
98
+ const generatedDefinitions = generateDefinitionConstants(lines, entries);
99
+ generateMap(lines, generatedDefinitions);
53
100
  lines.push(
101
+ "",
54
102
  `export const version = ${JSON.stringify(FLAGS_DEFINITIONS_VERSION)};`
55
103
  );
56
104
  return lines.join("\n");
57
105
  }
58
- async function prepareFlagsDefinitions(options) {
59
- const {
60
- cwd,
61
- env,
62
- userAgentSuffix,
63
- fetch: fetchFn = globalThis.fetch,
64
- output
65
- } = options;
66
- output == null ? void 0 : output.debug("vercel-flags: checking env vars for SDK Keys");
106
+ var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
107
+ function collectFlagEntries(env, output) {
108
+ const entries = [];
67
109
  const sdkKeys = Array.from(
68
110
  Object.values(env).reduce((acc, value) => {
69
111
  if (typeof value === "string") {
70
- if (value.startsWith("vf_")) {
112
+ if (SDK_KEY_REGEX.test(value)) {
71
113
  acc.add(value);
72
114
  } else if (value.startsWith("flags:")) {
73
115
  const params = new URLSearchParams(value.slice("flags:".length));
74
116
  const sdkKey = params.get("sdkKey");
75
- if (sdkKey == null ? void 0 : sdkKey.startsWith("vf_")) {
117
+ if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) {
76
118
  acc.add(sdkKey);
77
119
  }
78
120
  }
@@ -80,48 +122,68 @@ async function prepareFlagsDefinitions(options) {
80
122
  return acc;
81
123
  }, /* @__PURE__ */ new Set())
82
124
  );
83
- output == null ? void 0 : output.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);
84
- if (sdkKeys.length === 0) {
85
- return { created: false, reason: "no-sdk-keys" };
125
+ if (sdkKeys.length > 0) {
126
+ output == null ? void 0 : output.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);
127
+ for (const key of sdkKeys) {
128
+ entries.push({ type: "sdkKey", key });
129
+ }
86
130
  }
87
- const fetchPromise = Promise.all(
88
- sdkKeys.map(async (sdkKey) => {
89
- const headers = {
90
- authorization: `Bearer ${sdkKey}`,
91
- "user-agent": [
92
- `@vercel/prepare-flags-definitions/${version}`,
93
- userAgentSuffix
94
- ].filter(Boolean).join(" ")
95
- };
96
- if (env.VERCEL_PROJECT_ID) {
97
- headers["x-vercel-project-id"] = env.VERCEL_PROJECT_ID;
98
- }
99
- if (env.VERCEL_ENV) {
100
- headers["x-vercel-env"] = env.VERCEL_ENV;
101
- }
102
- if (env.VERCEL_DEPLOYMENT_ID) {
103
- headers["x-vercel-deployment-id"] = env.VERCEL_DEPLOYMENT_ID;
131
+ const oidcToken = env.VERCEL_OIDC_TOKEN;
132
+ if (oidcToken && (oidcToken == null ? void 0 : oidcToken.length) > 0) {
133
+ output == null ? void 0 : output.debug(`vercel-flags: found OIDC token`);
134
+ entries.push({ type: "oidcToken", key: oidcToken });
135
+ }
136
+ return entries;
137
+ }
138
+ async function prepareFlagsDefinitions(options) {
139
+ const {
140
+ cwd,
141
+ env,
142
+ userAgentSuffix,
143
+ fetch: fetchFn = globalThis.fetch,
144
+ output
145
+ } = options;
146
+ output == null ? void 0 : output.debug("vercel-flags: checking env vars for SDK Keys and OIDC Token");
147
+ const entries = collectFlagEntries(env, output);
148
+ if (entries.length === 0) {
149
+ return { created: false, reason: "no-flags-entries" };
150
+ }
151
+ const resolvedEntries = await Promise.all(
152
+ entries.map(async ({ key, type }) => {
153
+ const definitions = await fetchDatafile(
154
+ key,
155
+ env,
156
+ fetchFn,
157
+ userAgentSuffix
158
+ );
159
+ if (!definitions) {
160
+ return;
104
161
  }
105
- if (env.VERCEL_REGION) {
106
- headers["x-vercel-region"] = env.VERCEL_REGION;
162
+ if (type === "oidcToken") {
163
+ const projectId = getProjectIdFromOidcToken(key);
164
+ if (projectId) {
165
+ return {
166
+ key: projectId,
167
+ definitions
168
+ };
169
+ }
107
170
  }
108
- const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });
109
- if (!res.ok) {
110
- throw new Error(
111
- `Failed to fetch flag definitions for ${obfuscate(sdkKey)}: ${res.status} ${res.statusText}`
112
- );
171
+ if (type === "sdkKey") {
172
+ return {
173
+ key: hashSdkKey(key),
174
+ definitions
175
+ };
113
176
  }
114
- return res.json();
115
177
  })
116
178
  );
117
- const values = output ? await output.time("vercel-flags: load datafiles", fetchPromise) : await fetchPromise;
118
- const definitionsJs = generateDefinitionsModule(sdkKeys, values);
179
+ const validEntries = resolvedEntries.filter((entry) => !!entry);
180
+ const definitionsJs = generateDefinitionsModule(validEntries, output);
119
181
  const storageDir = join(cwd, "node_modules", "@vercel", "flags-definitions");
120
182
  const indexPath = join(storageDir, "index.js");
121
183
  const dtsPath = join(storageDir, "index.d.ts");
122
184
  const packageJsonPath = join(storageDir, "package.json");
123
185
  const dts = [
124
- "export function get(hashedSdkKey: string): Record<string, unknown> | null;",
186
+ "export function get(key: string): Record<string, unknown> | null;",
125
187
  "export const version: string;",
126
188
  ""
127
189
  ].join("\n");
@@ -148,13 +210,11 @@ async function prepareFlagsDefinitions(options) {
148
210
  output == null ? void 0 : output.debug(` \u2192 ${indexPath}`);
149
211
  output == null ? void 0 : output.debug(` \u2192 ${dtsPath}`);
150
212
  output == null ? void 0 : output.debug(` \u2192 ${packageJsonPath}`);
151
- output == null ? void 0 : output.debug(
152
- ` \u2192 included definitions for keys "${sdkKeys.map((key) => obfuscate(key)).join(", ")}"`
153
- );
154
- return { created: true, sdkKeysCount: sdkKeys.length };
213
+ return { created: true, entryCount: entries.length };
155
214
  }
156
215
  export {
157
216
  generateDefinitionsModule,
217
+ getProjectIdFromOidcToken,
158
218
  hashSdkKey,
159
219
  prepareFlagsDefinitions
160
220
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../package.json"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { version as PACKAGE_VERSION } from '../package.json';\n\nconst FLAGS_HOST = 'https://flags.vercel.com';\nconst FLAGS_DEFINITIONS_VERSION = '1.0.1';\n\ntype BundledDefinitions = Record<string, unknown>;\n\nexport interface Output {\n debug(message: string): void;\n time<T>(label: string, promise: Promise<T>): Promise<T>;\n}\n\nexport type PrepareFlagsDefinitionsResult =\n | { created: false; reason: 'no-sdk-keys' }\n | { created: true; sdkKeysCount: number };\n\n/**\n * Obfuscates SDK key for logging (shows first 18 chars)\n */\nfunction obfuscate(sdkKey: string, prefixLength = 18): string {\n if (prefixLength >= sdkKey.length) return sdkKey;\n return (\n sdkKey.slice(0, prefixLength) + '*'.repeat(sdkKey.length - prefixLength)\n );\n}\n\n/**\n * Computes a SHA-256 hex digest of the given SDK key.\n */\nexport function hashSdkKey(sdkKey: string): string {\n return createHash('sha256').update(sdkKey).digest('hex');\n}\n\n/**\n * Generates a JS module with deduplicated, lazily-parsed definitions.\n *\n * The map keys are SHA-256 hashes of the SDK keys so that raw keys\n * are not embedded in the output.\n *\n * Output format:\n * ```js\n * const memo = (fn) => { let cached; return () => (cached ??= fn()); };\n * const _d0 = memo(() => JSON.parse('...'));\n * const map = { \"<sha256_hash>\": _d0 };\n * export function get(hashedSdkKey) { return map[hashedSdkKey]?.() ?? null; }\n * ```\n */\nexport function generateDefinitionsModule(\n sdkKeys: string[],\n values: BundledDefinitions[],\n): string {\n // Stringify each definition\n const stringified = sdkKeys.map((_, i) => JSON.stringify(values[i]));\n\n // Deduplicate: map unique strings to indices\n const uniqueStrings: string[] = [];\n const stringToIndex = new Map<string, number>();\n for (const s of stringified) {\n if (!stringToIndex.has(s)) {\n stringToIndex.set(s, uniqueStrings.length);\n uniqueStrings.push(s);\n }\n }\n\n // Map SDK keys to their definition index\n const keyToIndex = sdkKeys.map(\n (_, i) => stringToIndex.get(stringified[i]!) ?? 0,\n );\n\n // Hash each SDK key\n const hashedKeys = sdkKeys.map(hashSdkKey);\n\n // Generate JS\n const lines: string[] = [\n 'const memo = (fn) => { let cached; return () => (cached ??= fn()); };',\n '',\n ];\n\n // Add definition constants\n for (let i = 0; i < uniqueStrings.length; i++) {\n lines.push(\n `const _d${i} = memo(() => JSON.parse(${JSON.stringify(uniqueStrings[i])}));`,\n );\n }\n\n lines.push('');\n lines.push('const map = {');\n for (let i = 0; i < sdkKeys.length; i++) {\n lines.push(` ${JSON.stringify(hashedKeys[i])}: _d${keyToIndex[i]},`);\n }\n lines.push('};');\n lines.push('');\n lines.push('export function get(hashedSdkKey) {');\n lines.push(' return map[hashedSdkKey]?.() ?? null;');\n lines.push('}');\n lines.push('');\n lines.push(\n `export const version = ${JSON.stringify(FLAGS_DEFINITIONS_VERSION)};`,\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Prepares flag definitions by reading SDK keys from environment variables,\n * fetching definitions from flags.vercel.com, and writing them into a\n * synthetic `@vercel/flags-definitions` package inside `node_modules/`.\n */\nexport async function prepareFlagsDefinitions(options: {\n cwd: string;\n env: Record<string, string | undefined>;\n /**\n * Appended to the user-agent header to identify the caller.\n * Example: `\"vercel-cli/35.0.0\"`\n */\n userAgentSuffix?: string;\n fetch?: typeof globalThis.fetch;\n output?: Output;\n}): Promise<PrepareFlagsDefinitionsResult> {\n const {\n cwd,\n env,\n userAgentSuffix,\n fetch: fetchFn = globalThis.fetch,\n output,\n } = options;\n\n output?.debug('vercel-flags: checking env vars for SDK Keys');\n\n // Collect unique SDK keys from environment variables\n // Supports both direct SDK keys (vf_ prefix) and flags: format\n const sdkKeys = Array.from(\n Object.values(env).reduce<Set<string>>((acc, value) => {\n if (typeof value === 'string') {\n if (value.startsWith('vf_')) {\n acc.add(value);\n } else if (value.startsWith('flags:')) {\n const params = new URLSearchParams(value.slice('flags:'.length));\n const sdkKey = params.get('sdkKey');\n if (sdkKey?.startsWith('vf_')) {\n acc.add(sdkKey);\n }\n }\n }\n return acc;\n }, new Set<string>()),\n );\n\n output?.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);\n\n if (sdkKeys.length === 0) {\n return { created: false, reason: 'no-sdk-keys' };\n }\n\n // Fetch definitions for each SDK key\n const fetchPromise = Promise.all(\n sdkKeys.map(async (sdkKey) => {\n const headers: Record<string, string> = {\n authorization: `Bearer ${sdkKey}`,\n 'user-agent': [\n `@vercel/prepare-flags-definitions/${PACKAGE_VERSION}`,\n userAgentSuffix,\n ]\n .filter(Boolean)\n .join(' '),\n };\n\n // Add Vercel metadata headers if available\n if (env.VERCEL_PROJECT_ID) {\n headers['x-vercel-project-id'] = env.VERCEL_PROJECT_ID;\n }\n if (env.VERCEL_ENV) {\n headers['x-vercel-env'] = env.VERCEL_ENV;\n }\n if (env.VERCEL_DEPLOYMENT_ID) {\n headers['x-vercel-deployment-id'] = env.VERCEL_DEPLOYMENT_ID;\n }\n if (env.VERCEL_REGION) {\n headers['x-vercel-region'] = env.VERCEL_REGION;\n }\n\n const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });\n\n if (!res.ok) {\n throw new Error(\n `Failed to fetch flag definitions for ${obfuscate(sdkKey)}: ${res.status} ${res.statusText}`,\n );\n }\n\n return res.json() as Promise<BundledDefinitions>;\n }),\n );\n\n const values = output\n ? await output.time('vercel-flags: load datafiles', fetchPromise)\n : await fetchPromise;\n\n // Generate the JS module\n const definitionsJs = generateDefinitionsModule(sdkKeys, values);\n\n // Write to node_modules/@vercel/flags-definitions/\n const storageDir = join(cwd, 'node_modules', '@vercel', 'flags-definitions');\n const indexPath = join(storageDir, 'index.js');\n const dtsPath = join(storageDir, 'index.d.ts');\n const packageJsonPath = join(storageDir, 'package.json');\n\n const dts = [\n 'export function get(hashedSdkKey: string): Record<string, unknown> | null;',\n 'export const version: string;',\n '',\n ].join('\\n');\n\n const packageJson = {\n name: '@vercel/flags-definitions',\n version: FLAGS_DEFINITIONS_VERSION,\n type: 'module',\n main: './index.js',\n types: './index.d.ts',\n exports: {\n '.': {\n types: './index.d.ts',\n import: './index.js',\n },\n },\n };\n\n await mkdir(storageDir, { recursive: true });\n await Promise.all([\n writeFile(indexPath, definitionsJs),\n writeFile(dtsPath, dts),\n writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)),\n ]);\n\n output?.debug('vercel-flags: created module');\n output?.debug(` → ${indexPath}`);\n output?.debug(` → ${dtsPath}`);\n output?.debug(` → ${packageJsonPath}`);\n output?.debug(\n ` → included definitions for keys \"${sdkKeys.map((key) => obfuscate(key)).join(', ')}\"`,\n );\n\n return { created: true, sdkKeysCount: sdkKeys.length };\n}\n","{\n \"name\": \"@vercel/prepare-flags-definitions\",\n \"version\": \"0.2.0\",\n \"description\": \"\",\n \"keywords\": [],\n \"license\": \"MIT\",\n \"author\": \"\",\n \"sideEffects\": false,\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \".\": [\n \"dist/*.d.ts\",\n \"dist/*.d.cts\"\n ]\n }\n },\n \"files\": [\n \"dist\",\n \"CHANGELOG.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"check\": \"biome check\",\n \"test\": \"vitest --run\",\n \"test:watch\": \"vitest\",\n \"type-check\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@types/node\": \"20.11.17\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"5.6.3\",\n \"vite\": \"6.4.1\",\n \"vitest\": \"2.1.9\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../package.json"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { version as PACKAGE_VERSION } from '../package.json';\n\nconst FLAGS_HOST = 'https://flags.vercel.com';\nconst FLAGS_DEFINITIONS_VERSION = '1.0.1';\n\ntype BundledDefinitions = Record<string, unknown>;\n\nexport interface Output {\n debug(message: string): void;\n time<T>(label: string, promise: Promise<T>): Promise<T>;\n}\n\nexport type PrepareFlagsDefinitionsResult =\n | { created: false; reason: 'no-flags-entries' }\n | { created: true; entryCount: number };\n\n/**\n * Obfuscates SDK key for logging (shows first 18 chars)\n */\nfunction obfuscate(sdkKey: string, prefixLength = 18): string {\n if (prefixLength >= sdkKey.length) return sdkKey;\n return (\n sdkKey.slice(0, prefixLength) + '*'.repeat(sdkKey.length - prefixLength)\n );\n}\n\n/**\n * Computes a SHA-256 hex digest of the given SDK key.\n */\nexport function hashSdkKey(sdkKey: string): string {\n return createHash('sha256').update(sdkKey).digest('hex');\n}\n\nexport function getProjectIdFromOidcToken(oidcToken: string) {\n const tokenParts = oidcToken.split('.');\n if (tokenParts.length !== 3 || !tokenParts[1]) {\n return;\n }\n\n const payload = JSON.parse(\n Buffer.from(tokenParts[1], 'base64url').toString('utf8'),\n ) as { project_id?: unknown };\n\n if (typeof payload.project_id !== 'string' || !payload.project_id) {\n return;\n }\n\n return payload.project_id;\n}\n\ntype DefinitionsEntry = {\n key: string;\n definitions: BundledDefinitions;\n};\n\ntype MapEntry = {\n key: string;\n value: string;\n};\n\n/**\n * Creates js constants pointing to memoized deduplicated flag definitions.\n * Output format:\n * ```js\n * const _d0 = memo(() => JSON.parse('...'));\n * const _d1 = memo(() => JSON.parse('...'));\n * ````\n */\nfunction generateDefinitionConstants(\n lines: string[],\n entries: DefinitionsEntry[],\n): MapEntry[] {\n const stringToConst = new Map<string, string>();\n\n return entries.map((entry) => {\n const stringified = JSON.stringify(entry.definitions);\n let definitionConst = stringToConst.get(stringified);\n\n if (!definitionConst) {\n definitionConst = `_d${stringToConst.size}`;\n stringToConst.set(stringified, definitionConst);\n lines.push(\n `const ${definitionConst} = memo(() => JSON.parse(${JSON.stringify(stringified)}));`,\n );\n }\n\n return { key: entry.key, value: definitionConst };\n });\n}\n\n/**\n * Creates a js map and getter function for exposing key value pairs.\n * Output format:\n * ```js\n * const map = { \"<sha256_hash_or_project_id>\": _d0 };\n * export function get(key) { return map[key]?.() ?? null; }\n * ````\n */\nfunction generateMap(lines: string[], entries: MapEntry[]): void {\n lines.push('');\n lines.push('const map = {');\n for (const entry of entries) {\n lines.push(` ${JSON.stringify(entry.key)}: ${entry.value},`);\n }\n lines.push('};');\n lines.push('');\n lines.push('export function get(key) {');\n lines.push(' return map[key]?.() ?? null;');\n lines.push('}');\n}\n\nasync function fetchDatafile(\n token: string,\n env: Record<string, string | undefined>,\n fetchFn: typeof globalThis.fetch,\n userAgentSuffix?: string,\n): Promise<BundledDefinitions | undefined> {\n const headers: Record<string, string> = {\n authorization: `Bearer ${token}`,\n 'user-agent': [\n `@vercel/prepare-flags-definitions/${PACKAGE_VERSION}`,\n userAgentSuffix,\n ]\n .filter(Boolean)\n .join(' '),\n };\n\n // Add Vercel metadata headers if available\n if (env.VERCEL_PROJECT_ID) {\n headers['x-vercel-project-id'] = env.VERCEL_PROJECT_ID;\n }\n if (env.VERCEL_ENV) {\n headers['x-vercel-env'] = env.VERCEL_ENV;\n }\n if (env.VERCEL_DEPLOYMENT_ID) {\n headers['x-vercel-deployment-id'] = env.VERCEL_DEPLOYMENT_ID;\n }\n if (env.VERCEL_REGION) {\n headers['x-vercel-region'] = env.VERCEL_REGION;\n }\n\n const res = await fetchFn(`${FLAGS_HOST}/v1/datafile`, { headers });\n\n if (!res.ok) {\n if (res.status === 404) {\n return undefined;\n }\n\n throw new Error(\n `Failed to fetch flag definitions for ${obfuscate(token)}: ${res.status} ${res.statusText}`,\n );\n }\n\n return res.json() as Promise<BundledDefinitions>;\n}\n\n/**\n * Generates a JS module with deduplicated, lazily-parsed definitions.\n *\n * The map keys are SHA-256 hashes of the SDK keys so that raw keys\n * are not embedded in the output.\n *\n * Output format:\n * ```js\n * const memo = (fn) => { let cached; return () => (cached ??= fn()); };\n * const _d0 = memo(() => JSON.parse('...'));\n * const map = { \"<sha256_hash>\": _d0 };\n * export function get(hashedSdkKey) { return map[hashedSdkKey]?.() ?? null; }\n * ```\n */\nexport function generateDefinitionsModule(\n entries: DefinitionsEntry[],\n output: Output | undefined,\n): string {\n output?.debug(\n `vercel-flags: writing flag definitions for \"${entries.map(({ key }) => obfuscate(key)).join(', ')}\"`,\n );\n\n // generate shared js\n const lines: string[] = [\n 'const memo = (fn) => { let cached; return () => (cached ??= fn()); };',\n '',\n ];\n\n // generate js const and capture the const names\n const generatedDefinitions = generateDefinitionConstants(lines, entries);\n\n // generate a map wiring keys to const names\n generateMap(lines, generatedDefinitions);\n\n lines.push(\n '',\n `export const version = ${JSON.stringify(FLAGS_DEFINITIONS_VERSION)};`,\n );\n\n return lines.join('\\n');\n}\n\ntype FlagEntry = {\n type: 'oidcToken' | 'sdkKey';\n key: string;\n};\n\n/**\n * Regex to match valid Vercel Flags SDK keys.\n * SDK keys must follow the format: vf_server_* or vf_client_*\n * This avoids false positives with third-party identifiers that happen\n * to start with 'vf_' (e.g., Stripe identity flow IDs like 'vf_1PyH...').\n */\nconst SDK_KEY_REGEX = /^vf_(?:server|client)_/;\n\n/**\n * Collect all possible flag entries the need embedding from the environment\n */\nfunction collectFlagEntries(\n env: Record<string, string | undefined>,\n output: Output | undefined,\n): FlagEntry[] {\n const entries: FlagEntry[] = [];\n\n // Collect unique SDK keys from environment variables\n // Supports both direct SDK keys (vf_server_*/vf_client_*) and flags: format\n const sdkKeys = Array.from(\n Object.values(env).reduce<Set<string>>((acc, value) => {\n if (typeof value === 'string') {\n if (SDK_KEY_REGEX.test(value)) {\n acc.add(value);\n } else if (value.startsWith('flags:')) {\n const params = new URLSearchParams(value.slice('flags:'.length));\n const sdkKey = params.get('sdkKey');\n if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) {\n acc.add(sdkKey);\n }\n }\n }\n return acc;\n }, new Set<string>()),\n );\n\n if (sdkKeys.length > 0) {\n output?.debug(`vercel-flags: found ${sdkKeys.length} SDK keys`);\n\n for (const key of sdkKeys) {\n entries.push({ type: 'sdkKey', key });\n }\n }\n\n const oidcToken = env.VERCEL_OIDC_TOKEN;\n if (oidcToken && oidcToken?.length > 0) {\n output?.debug(`vercel-flags: found OIDC token`);\n\n entries.push({ type: 'oidcToken', key: oidcToken });\n }\n\n return entries;\n}\n\n/**\n * Prepares flag definitions by reading SDK keys from environment variables,\n * fetching definitions from flags.vercel.com, and writing them into a\n * synthetic `@vercel/flags-definitions` package inside `node_modules/`.\n */\nexport async function prepareFlagsDefinitions(options: {\n cwd: string;\n env: Record<string, string | undefined>;\n /**\n * Appended to the user-agent header to identify the caller.\n * Example: `\"vercel-cli/35.0.0\"`\n */\n userAgentSuffix?: string;\n fetch?: typeof globalThis.fetch;\n output?: Output;\n}): Promise<PrepareFlagsDefinitionsResult> {\n const {\n cwd,\n env,\n userAgentSuffix,\n fetch: fetchFn = globalThis.fetch,\n output,\n } = options;\n\n output?.debug('vercel-flags: checking env vars for SDK Keys and OIDC Token');\n\n const entries = collectFlagEntries(env, output);\n if (entries.length === 0) {\n return { created: false, reason: 'no-flags-entries' };\n }\n\n // fetch all datafiles for sdk keys and oidc tokens\n const resolvedEntries = await Promise.all(\n entries.map(async ({ key, type }) => {\n const definitions = await fetchDatafile(\n key,\n env,\n fetchFn,\n userAgentSuffix,\n );\n if (!definitions) {\n return;\n }\n\n if (type === 'oidcToken') {\n const projectId = getProjectIdFromOidcToken(key);\n\n if (projectId) {\n return {\n key: projectId,\n definitions,\n };\n }\n }\n\n if (type === 'sdkKey') {\n return {\n key: hashSdkKey(key),\n definitions,\n };\n }\n }),\n );\n\n const validEntries = resolvedEntries.filter((entry) => !!entry);\n\n const definitionsJs = generateDefinitionsModule(validEntries, output);\n\n // Write to node_modules/@vercel/flags-definitions/\n const storageDir = join(cwd, 'node_modules', '@vercel', 'flags-definitions');\n const indexPath = join(storageDir, 'index.js');\n const dtsPath = join(storageDir, 'index.d.ts');\n const packageJsonPath = join(storageDir, 'package.json');\n\n const dts = [\n 'export function get(key: string): Record<string, unknown> | null;',\n 'export const version: string;',\n '',\n ].join('\\n');\n\n const packageJson = {\n name: '@vercel/flags-definitions',\n version: FLAGS_DEFINITIONS_VERSION,\n type: 'module',\n main: './index.js',\n types: './index.d.ts',\n exports: {\n '.': {\n types: './index.d.ts',\n import: './index.js',\n },\n },\n };\n\n await mkdir(storageDir, { recursive: true });\n await Promise.all([\n writeFile(indexPath, definitionsJs),\n writeFile(dtsPath, dts),\n writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)),\n ]);\n\n output?.debug('vercel-flags: created module');\n output?.debug(` → ${indexPath}`);\n output?.debug(` → ${dtsPath}`);\n output?.debug(` → ${packageJsonPath}`);\n\n return { created: true, entryCount: entries.length };\n}\n","{\n \"name\": \"@vercel/prepare-flags-definitions\",\n \"version\": \"0.3.0-1602289-20260528072704\",\n \"description\": \"A utility for preparing flags definitions for embedding\",\n \"keywords\": [\n \"flags\",\n \"flags sdk\",\n \"experimentation\",\n \"ab testing\"\n ],\n \"license\": \"MIT\",\n \"author\": \"Dominik Ferber <dominik@vercel.com>\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/vercel/flags.git\"\n },\n \"sideEffects\": false,\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \".\": [\n \"dist/*.d.ts\",\n \"dist/*.d.cts\"\n ]\n }\n },\n \"files\": [\n \"dist\",\n \"CHANGELOG.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"check\": \"biome check\",\n \"test\": \"vitest --run\",\n \"test:watch\": \"vitest\",\n \"type-check\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@types/node\": \"20.11.17\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"5.6.3\",\n \"vite\": \"6.4.1\",\n \"vitest\": \"2.1.9\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "@vercel/prepare-flags-definitions",
3
- "version": "0.2.0",
4
- "description": "",
5
- "keywords": [],
3
+ "version": "0.3.0-1602289-20260528072704",
4
+ "description": "A utility for preparing flags definitions for embedding",
5
+ "keywords": [
6
+ "flags",
7
+ "flags sdk",
8
+ "experimentation",
9
+ "ab testing"
10
+ ],
6
11
  "license": "MIT",
7
- "author": "",
12
+ "author": "Dominik Ferber <dominik@vercel.com>",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/vercel/flags.git"
16
+ },
8
17
  "sideEffects": false,
9
18
  "type": "module",
10
19
  "exports": {