flaghoist 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import { parse } from "smol-toml";
3
3
  var DEFAULT_CONFIG = {
4
4
  name: "team-flags",
5
5
  storage: "cloudflare-kv",
6
+ platform: "cloudflare",
6
7
  auth: { admin: "bearer-token", read: "api-key" },
7
8
  dashboard: true
8
9
  };
@@ -10,23 +11,35 @@ var STORAGE_KINDS = [
10
11
  "cloudflare-kv",
11
12
  "redis",
12
13
  "postgres",
14
+ "sqlite",
13
15
  "memory"
14
16
  ];
17
+ var PLATFORM_KINDS = ["cloudflare", "container"];
18
+ function containerStorageDefault(storage) {
19
+ return storage === "cloudflare-kv" ? "postgres" : storage;
20
+ }
21
+ function asContainer(config) {
22
+ return { ...config, platform: "container", storage: containerStorageDefault(config.storage) };
23
+ }
15
24
  function parseConfig(text) {
16
25
  const raw = parse(text);
17
26
  const name = typeof raw.name === "string" && raw.name ? raw.name : DEFAULT_CONFIG.name;
18
27
  const storage = STORAGE_KINDS.includes(raw.storage) ? raw.storage : DEFAULT_CONFIG.storage;
28
+ const platform = raw.platform === "container" ? "container" : DEFAULT_CONFIG.platform;
19
29
  const authRaw = typeof raw.auth === "object" && raw.auth !== null ? raw.auth : {};
20
30
  const admin = authRaw.admin === "oidc" ? "oidc" : "bearer-token";
21
31
  const allowedOrigins = Array.isArray(raw.allowedOrigins) ? raw.allowedOrigins.filter((x) => typeof x === "string") : void 0;
22
32
  const dashboard = raw.dashboard !== false;
23
- return { name, storage, auth: { admin, read: "api-key" }, allowedOrigins, dashboard };
33
+ return { name, storage, platform, auth: { admin, read: "api-key" }, allowedOrigins, dashboard };
24
34
  }
25
35
  function serializeConfig(config) {
26
36
  const lines = [
27
37
  `name = ${JSON.stringify(config.name)}`,
28
38
  `storage = ${JSON.stringify(config.storage)}`
29
39
  ];
40
+ if (config.platform !== "cloudflare") {
41
+ lines.push(`platform = ${JSON.stringify(config.platform)}`);
42
+ }
30
43
  if (config.allowedOrigins && config.allowedOrigins.length > 0) {
31
44
  lines.push(`allowedOrigins = ${JSON.stringify(config.allowedOrigins)}`);
32
45
  }
@@ -46,6 +59,9 @@ function serializeConfig(config) {
46
59
  export {
47
60
  DEFAULT_CONFIG,
48
61
  STORAGE_KINDS,
62
+ PLATFORM_KINDS,
63
+ containerStorageDefault,
64
+ asContainer,
49
65
  parseConfig,
50
66
  serializeConfig
51
67
  };
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_CONFIG,
4
+ PLATFORM_KINDS,
4
5
  STORAGE_KINDS,
6
+ asContainer,
7
+ containerStorageDefault,
5
8
  parseConfig,
6
9
  serializeConfig
7
- } from "./chunk-S4XXPMMW.js";
10
+ } from "./chunk-HSDDPXCM.js";
8
11
 
9
12
  // src/index.ts
10
13
  import { spawnSync } from "child_process";
@@ -14,78 +17,149 @@ import { createInterface } from "readline/promises";
14
17
  import { parseArgs } from "util";
15
18
 
16
19
  // src/admin.ts
17
- function createAdminClient(options) {
18
- const doFetch = options.fetch ?? ((input, init) => fetch(input, init));
19
- const api = `${options.url.replace(/\/+$/, "")}/api/v1`;
20
- const headers = {
21
- authorization: `Bearer ${options.token}`,
22
- "content-type": "application/json"
23
- };
24
- const path = (key) => `${api}/flags/${encodeURIComponent(key)}`;
25
- return {
26
- async list() {
27
- const res = await doFetch(`${api}/flags`, { headers });
28
- if (!res.ok) throw new Error(`Failed to list flags (${res.status})`);
29
- return (await res.json()).flags;
30
- },
31
- async get(key) {
32
- const res = await doFetch(path(key), { headers });
33
- if (res.status === 404) return null;
34
- if (!res.ok) throw new Error(`Failed to read flag "${key}" (${res.status})`);
35
- return await res.json();
36
- },
37
- async put(key, input) {
38
- const res = await doFetch(path(key), { method: "PUT", headers, body: JSON.stringify(input) });
39
- if (!res.ok)
40
- throw new Error(`Failed to save flag "${key}" (${res.status}): ${await res.text()}`);
41
- return await res.json();
42
- },
43
- async delete(key) {
44
- const res = await doFetch(path(key), { method: "DELETE", headers });
45
- if (!res.ok && res.status !== 404)
46
- throw new Error(`Failed to delete flag "${key}" (${res.status})`);
47
- }
48
- };
20
+ import {
21
+ createAdminClient,
22
+ createFlag,
23
+ setRollout,
24
+ setRules,
25
+ toggleFlag
26
+ } from "@flaghoist/admin-client";
27
+
28
+ // src/generate-container.ts
29
+ var CONTAINER_DEPS = {
30
+ "@flaghoist/adapter-memory": "^0.1.2",
31
+ "@flaghoist/adapter-postgres": "^0.1.2",
32
+ "@flaghoist/adapter-redis": "^0.1.2",
33
+ "@flaghoist/adapter-sqlite": "^0.1.0",
34
+ "@flaghoist/server": "^0.3.0",
35
+ "@hono/node-server": "^2.1.1",
36
+ "better-sqlite3": "^11.0.0",
37
+ ioredis: "^5.4.0",
38
+ pg: "^8.13.0"
39
+ };
40
+ var CONTAINER_PORT = 8080;
41
+ function adminExpr(admin) {
42
+ return admin === "oidc" ? "oidc({ issuer: env.OIDC_ISSUER, audience: env.OIDC_AUDIENCE, groupsClaim: 'cognito:groups', allowedGroups: (env.ADMIN_GROUPS ?? '').split(',') })" : "bearerToken(env.ADMIN_TOKEN)";
49
43
  }
50
- function requireFlag(flag, key) {
51
- if (!flag) throw new Error(`Flag "${key}" not found`);
52
- return flag;
44
+ function generateNodeEntry(config) {
45
+ const defaultStorage = containerStorageDefault(config.storage);
46
+ const serverImports = [
47
+ "apiKey",
48
+ "createFlagServer",
49
+ "memoryRateLimit",
50
+ config.auth.admin === "oidc" ? "oidc" : "bearerToken"
51
+ ].sort();
52
+ const imports = [
53
+ `import { memoryAdapter } from '@flaghoist/adapter-memory'`,
54
+ `import { initPostgres, postgresAdapter } from '@flaghoist/adapter-postgres'`,
55
+ `import { redisAdapter } from '@flaghoist/adapter-redis'`,
56
+ `import { initSqlite, sqliteAdapter } from '@flaghoist/adapter-sqlite'`,
57
+ `import { ${serverImports.join(", ")} } from '@flaghoist/server'`,
58
+ ...config.dashboard ? [`import { dashboardHtml } from '@flaghoist/server/dashboard'`] : [],
59
+ `import { serve } from '@hono/node-server'`
60
+ ].join("\n");
61
+ const dashboard = config.dashboard ? "\n dashboard: dashboardHtml," : "";
62
+ const origins = config.allowedOrigins && config.allowedOrigins.length > 0 ? JSON.stringify(config.allowedOrigins) : "undefined";
63
+ return `${imports}
64
+
65
+ // One image, any host. Everything is driven by environment variables, so the same container runs on
66
+ // a plain VPS, Fly.io, Railway, DigitalOcean, Cloud Run, ECS, or Kubernetes, with no code to edit.
67
+ const env = process.env
68
+ const kind = env.FLAGS_STORAGE ?? '${defaultStorage}'
69
+
70
+ async function makeStorage() {
71
+ if (kind === 'postgres') {
72
+ if (!env.DATABASE_URL) throw new Error('FLAGS_STORAGE=postgres requires DATABASE_URL')
73
+ const { Pool } = await import('pg')
74
+ const table = env.FLAGS_TABLE ?? 'flaghoist_flags'
75
+ const pool = new Pool({
76
+ connectionString: env.DATABASE_URL,
77
+ // Managed Postgres (Supabase, Neon, Render) requires TLS. Skip this if your URL sets sslmode.
78
+ ssl: /sslmode=/.test(env.DATABASE_URL) ? undefined : { rejectUnauthorized: false },
79
+ })
80
+ await initPostgres(pool, table)
81
+ return postgresAdapter(pool, { table })
82
+ }
83
+
84
+ if (kind === 'redis') {
85
+ if (!env.REDIS_URL) throw new Error('FLAGS_STORAGE=redis requires REDIS_URL')
86
+ const { default: Redis } = await import('ioredis')
87
+ return redisAdapter(new Redis(env.REDIS_URL), { hashKey: env.FLAGS_HASH_KEY ?? 'flaghoist:flags' })
88
+ }
89
+
90
+ if (kind === 'sqlite') {
91
+ const { default: Database } = await import('better-sqlite3')
92
+ const db = new Database(env.DATABASE_PATH ?? '/data/flags.db')
93
+ initSqlite(db)
94
+ return sqliteAdapter(db)
95
+ }
96
+
97
+ if (kind === 'memory') {
98
+ console.warn('[flaghoist] FLAGS_STORAGE=memory: flags are in-process and are lost on restart.')
99
+ return memoryAdapter()
100
+ }
101
+
102
+ throw new Error(\`Unknown FLAGS_STORAGE "\${kind}". Use postgres, redis, sqlite, or memory.\`)
53
103
  }
54
- function createFlag(client, key, opts) {
55
- return client.put(key, {
56
- enabled: opts.enabled ?? false,
57
- rollout: { percentage: opts.percentage ?? 0 },
58
- rules: [],
59
- description: opts.description ?? ""
60
- });
104
+
105
+ const app = createFlagServer({
106
+ storage: await makeStorage(),${dashboard}
107
+ auth: {
108
+ admin: ${adminExpr(config.auth.admin)},
109
+ read: apiKey(env.READ_API_KEY),
110
+ },
111
+ rateLimit: memoryRateLimit(),
112
+ // Comma-separated list of browser origins allowed to read flags cross-origin.
113
+ allowedOrigins: env.FLAGS_CORS ? env.FLAGS_CORS.split(',').map((o) => o.trim()) : ${origins},
114
+ })
115
+
116
+ const port = Number(env.PORT ?? ${CONTAINER_PORT})
117
+ serve({ fetch: app.fetch, port, hostname: '0.0.0.0' })
118
+ console.log(\`[flaghoist] listening on :\${port} (storage: \${kind})\`)
119
+ `;
61
120
  }
62
- async function toggleFlag(client, key, to) {
63
- const flag = requireFlag(await client.get(key), key);
64
- const enabled = to === "flip" ? !flag.enabled : to;
65
- return client.put(key, {
66
- enabled,
67
- rollout: flag.rollout,
68
- rules: flag.rules,
69
- description: flag.description
70
- });
121
+ function generateDockerfile(config) {
122
+ const defaultStorage = containerStorageDefault(config.storage);
123
+ return `# Flaghoist as a portable container. Build once, run on any host: a VPS, Fly.io, Railway,
124
+ # DigitalOcean, Cloud Run, ECS, or Kubernetes. Configure it entirely with environment variables.
125
+ FROM node:22-alpine
126
+
127
+ WORKDIR /app
128
+
129
+ # Install the published Flaghoist packages. Copying only package.json first keeps this layer cached
130
+ # across code changes.
131
+ COPY package.json ./
132
+ RUN npm install --omit=dev --no-audit --no-fund
133
+
134
+ COPY server.mjs ./
135
+
136
+ ENV PORT=${CONTAINER_PORT}
137
+ ENV FLAGS_STORAGE=${defaultStorage}
138
+ EXPOSE ${CONTAINER_PORT}
139
+
140
+ # The /health route is unauthenticated, so it makes a good container healthcheck.
141
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \\
142
+ CMD node -e "fetch('http://127.0.0.1:${CONTAINER_PORT}/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
143
+
144
+ CMD ["node", "server.mjs"]
145
+ `;
71
146
  }
72
- async function setRollout(client, key, percentage) {
73
- const flag = requireFlag(await client.get(key), key);
74
- return client.put(key, {
75
- enabled: flag.enabled,
76
- rollout: { percentage },
77
- rules: flag.rules,
78
- description: flag.description
79
- });
147
+ function generateContainerPackageJson(config) {
148
+ const pkg = {
149
+ name: config.name,
150
+ version: "0.0.0",
151
+ private: true,
152
+ type: "module",
153
+ scripts: { start: "node server.mjs" },
154
+ dependencies: CONTAINER_DEPS
155
+ };
156
+ return `${JSON.stringify(pkg, null, 2)}
157
+ `;
80
158
  }
81
- async function setRules(client, key, rules) {
82
- const flag = requireFlag(await client.get(key), key);
83
- return client.put(key, {
84
- enabled: flag.enabled,
85
- rollout: flag.rollout,
86
- rules,
87
- description: flag.description
88
- });
159
+ function generateDockerignore() {
160
+ return ["node_modules", "npm-debug.log", "Dockerfile", ".dockerignore", "README.md", ""].join(
161
+ "\n"
162
+ );
89
163
  }
90
164
 
91
165
  // src/generate.ts
@@ -123,9 +197,13 @@ function storageSnippet(storage) {
123
197
  expr: "memoryAdapter()",
124
198
  pkg: "@flaghoist/adapter-memory"
125
199
  };
200
+ case "sqlite":
201
+ throw new Error(
202
+ 'SQLite storage requires a Node or container deployment. Use `npx flaghoist deploy` and pick "Another platform".'
203
+ );
126
204
  }
127
205
  }
128
- function adminExpr(admin) {
206
+ function adminExpr2(admin) {
129
207
  return admin === "oidc" ? "oidc({ issuer: env.OIDC_ISSUER, audience: env.OIDC_AUDIENCE, groupsClaim: 'cognito:groups', allowedGroups: (env.ADMIN_GROUPS ?? '').split(',') })" : "bearerToken(env.ADMIN_TOKEN)";
130
208
  }
131
209
  function generateWorkerEntry(config) {
@@ -149,7 +227,7 @@ function generateWorkerEntry(config) {
149
227
  export default createFlagServer((env) => ({
150
228
  storage: ${storage.expr},
151
229
  auth: {
152
- admin: ${adminExpr(config.auth.admin)},
230
+ admin: ${adminExpr2(config.auth.admin)},
153
231
  read: apiKey(env.READ_API_KEY),
154
232
  },${origins}${dashboard}
155
233
  }))
@@ -317,27 +395,49 @@ function runInit(args) {
317
395
  const { values } = parseArgs({
318
396
  args,
319
397
  allowPositionals: true,
320
- options: { name: { type: "string" }, storage: { type: "string" } }
398
+ options: {
399
+ name: { type: "string" },
400
+ storage: { type: "string" },
401
+ platform: { type: "string" }
402
+ }
321
403
  });
322
404
  if (existsSync("flaghoist.toml")) throw new Error("flaghoist.toml already exists.");
323
405
  if (values.storage && !STORAGE_KINDS.includes(values.storage)) {
324
406
  throw new Error(`Unknown storage "${values.storage}". One of: ${STORAGE_KINDS.join(", ")}.`);
325
407
  }
326
- const config = {
408
+ if (values.platform && !PLATFORM_KINDS.includes(values.platform)) {
409
+ throw new Error(`Unknown platform "${values.platform}". One of: ${PLATFORM_KINDS.join(", ")}.`);
410
+ }
411
+ const base = {
327
412
  ...DEFAULT_CONFIG,
328
413
  name: values.name ?? DEFAULT_CONFIG.name,
329
414
  storage: values.storage ?? DEFAULT_CONFIG.storage
330
415
  };
416
+ const config = values.platform === "container" ? asContainer(base) : base;
331
417
  writeFileSync("flaghoist.toml", serializeConfig(config));
332
418
  console.log("Created flaghoist.toml");
333
419
  console.log("Next: `flaghoist deploy` to ship it, or `flaghoist eject` to own the code.");
334
420
  }
335
- function writeProject(config, dir) {
336
- const files = [
421
+ function entryFile(platform) {
422
+ return platform === "container" ? "server.mjs" : "src/index.ts";
423
+ }
424
+ function projectFiles(config) {
425
+ if (config.platform === "container") {
426
+ return [
427
+ ["server.mjs", generateNodeEntry(config)],
428
+ ["Dockerfile", generateDockerfile(config)],
429
+ [".dockerignore", generateDockerignore()],
430
+ ["package.json", generateContainerPackageJson(config)]
431
+ ];
432
+ }
433
+ return [
337
434
  ["src/index.ts", generateWorkerEntry(config)],
338
435
  ["wrangler.toml", generateWranglerToml(config)],
339
436
  ["package.json", generatePackageJson(config)]
340
437
  ];
438
+ }
439
+ function writeProject(config, dir) {
440
+ const files = projectFiles(config);
341
441
  const existing = files.map(([name]) => name).filter((name) => existsSync(join(dir, name)));
342
442
  if (existing.length > 0) {
343
443
  throw new Error(
@@ -350,8 +450,15 @@ Flaghoist deploys as its own service, so give it a directory of its own:
350
450
  }
351
451
  function runEject() {
352
452
  const config = loadConfig();
353
- if (existsSync("src/index.ts")) throw new Error("src/index.ts already exists. Already ejected?");
453
+ const entry = entryFile(config.platform);
454
+ if (existsSync(entry)) throw new Error(`${entry} already exists. Already ejected?`);
354
455
  writeProject(config, ".");
456
+ if (config.platform === "container") {
457
+ console.log("Ejected to a code project you own: server.mjs, Dockerfile, package.json");
458
+ console.log("Edit server.mjs freely, then build and run the container, or deploy to a host:");
459
+ console.log(" https://docs.flaghoist.dev/deploy/overview/");
460
+ return;
461
+ }
355
462
  console.log("Ejected to a code project you own: src/index.ts, wrangler.toml, package.json");
356
463
  if (config.storage === "cloudflare-kv") {
357
464
  console.log("Create your KV namespace and paste the id into wrangler.toml:");
@@ -412,30 +519,30 @@ async function promptDeployTarget() {
412
519
  rl.close();
413
520
  }
414
521
  }
415
- function printOtherTargets() {
522
+ function deployContainer(config) {
523
+ const container = asContainer(config);
524
+ if (config.platform !== container.platform || config.storage !== container.storage) {
525
+ writeFileSync("flaghoist.toml", serializeConfig(container));
526
+ }
527
+ if (!existsSync(entryFile("container"))) writeProject(container, ".");
416
528
  console.log(`
417
- Flaghoist runs on any Node, Bun, Deno, or container host, not just Cloudflare.
529
+ Scaffolded a container project: server.mjs, Dockerfile, package.json.
418
530
 
419
- The project scaffolded here is a Cloudflare Worker. To run it elsewhere you serve the same
420
- createFlagServer() app on Node and point it at Postgres or Redis instead of Workers KV.
531
+ It runs on any container or Node host, configured by environment variables (FLAGS_STORAGE,
532
+ DATABASE_URL, ADMIN_TOKEN, READ_API_KEY). Build and run it with Docker:
421
533
 
422
- Guides:
534
+ docker build -t ${container.name} .
535
+ docker run -p 8080:8080 -e ADMIN_TOKEN=... -e READ_API_KEY=... ${container.name}
536
+
537
+ Or deploy it to a host:
423
538
  Render https://docs.flaghoist.dev/deploy/render/
539
+ Fly.io https://docs.flaghoist.dev/deploy/fly/
540
+ Railway https://docs.flaghoist.dev/deploy/railway/
424
541
  All targets https://docs.flaghoist.dev/deploy/overview/
425
542
 
426
- More platforms are on the way. Missing one you need? Open an issue at
427
- https://github.com/flaghoist/flaghoist/issues.`);
543
+ Missing a platform you need? Open an issue at https://github.com/flaghoist/flaghoist/issues.`);
428
544
  }
429
- async function runDeploy(args) {
430
- let target = parseDeployTarget(args);
431
- if (target === null) {
432
- target = process.stdin.isTTY ? await promptDeployTarget() : "cloudflare";
433
- }
434
- if (target === "other") {
435
- printOtherTargets();
436
- return;
437
- }
438
- const config = loadConfig();
545
+ async function deployCloudflare(config) {
439
546
  if (!existsSync("src/index.ts")) writeProject(config, ".");
440
547
  ensureDependencies();
441
548
  ensureKvNamespace(config);
@@ -443,14 +550,24 @@ async function runDeploy(args) {
443
550
  const result = spawnSync("npx", ["wrangler", "deploy"], { stdio: "inherit" });
444
551
  process.exit(result.status ?? 0);
445
552
  }
553
+ async function runDeploy(args) {
554
+ const config = loadConfig();
555
+ let target = parseDeployTarget(args);
556
+ if (target === null) {
557
+ if (config.platform === "container") target = "other";
558
+ else target = process.stdin.isTTY ? await promptDeployTarget() : "cloudflare";
559
+ }
560
+ if (target === "other") return deployContainer(config);
561
+ return deployCloudflare(config);
562
+ }
446
563
  function printHelp() {
447
564
  console.log(`flaghoist ${VERSION} \u2014 hoist your own feature flags
448
565
 
449
566
  Usage: flaghoist <command>
450
567
 
451
568
  Scaffolding
452
- init [--name N] [--storage cloudflare-kv|redis|postgres|memory]
453
- eject Generate a code project you own
569
+ init [--name N] [--storage cloudflare-kv|redis|postgres|memory] [--platform cloudflare|container]
570
+ eject Generate a code project you own (a Worker, or a container)
454
571
  deploy [--target T] Deploy (prompts for the platform; T is cloudflare or other)
455
572
 
456
573
  Flag management (needs --url/--token or FLAGS_URL/FLAGS_ADMIN_TOKEN)
package/dist/lib.d.ts CHANGED
@@ -1,8 +1,17 @@
1
- type StorageKind = 'cloudflare-kv' | 'redis' | 'postgres' | 'memory';
1
+ type StorageKind = 'cloudflare-kv' | 'redis' | 'postgres' | 'sqlite' | 'memory';
2
2
  type AdminAuthKind = 'bearer-token' | 'oidc';
3
+ /**
4
+ * The shape of project `flaghoist deploy`/`eject` scaffolds. `cloudflare` is a Worker plus a
5
+ * `wrangler.toml`; `container` is a Node entry plus a `Dockerfile`, which runs on any container or
6
+ * Node host (Render, Fly, Railway, a VPS). Cloudflare is the default, so a config written before
7
+ * this key existed still scaffolds a Worker.
8
+ */
9
+ type PlatformKind = 'cloudflare' | 'container';
3
10
  interface FlaghoistConfig {
4
11
  name: string;
5
12
  storage: StorageKind;
13
+ /** Deploy shape to scaffold. Defaults to `cloudflare`. */
14
+ platform: PlatformKind;
6
15
  auth: {
7
16
  admin: AdminAuthKind;
8
17
  read: 'api-key';
@@ -14,9 +23,27 @@ interface FlaghoistConfig {
14
23
  declare const DEFAULT_CONFIG: FlaghoistConfig;
15
24
  /** Every storage backend selectable by name in `flaghoist.toml`. */
16
25
  declare const STORAGE_KINDS: readonly StorageKind[];
26
+ /** Every deploy shape selectable by name in `flaghoist.toml`. */
27
+ declare const PLATFORM_KINDS: readonly PlatformKind[];
28
+ /** The stores a container can reach. Cloudflare KV is a Worker binding, so it is not one of them. */
29
+ type ContainerStorage = 'postgres' | 'redis' | 'sqlite' | 'memory';
30
+ /**
31
+ * The storage a container project uses. Cloudflare KV cannot be reached off Workers, so a config
32
+ * that still names it (the scaffolding default) becomes postgres, the store every container deploy
33
+ * guide uses. This is the one rule for a container's storage: the CLI reuses it to write a coherent
34
+ * `flaghoist.toml`, and the generated entry bakes it as the `FLAGS_STORAGE` default. Every kind
35
+ * stays overridable at runtime via the env var.
36
+ */
37
+ declare function containerStorageDefault(storage: StorageKind): ContainerStorage;
38
+ /**
39
+ * Recast a config for the container platform, running its storage through the container rule so the
40
+ * result never names a store the platform cannot use. This is the one place the platform switch
41
+ * rewrites a config, so `init`, `create-flaghoist`, `deploy` and `eject` all stay consistent.
42
+ */
43
+ declare function asContainer(config: FlaghoistConfig): FlaghoistConfig;
17
44
  /** Parse a `flaghoist.toml` into a validated config, falling back to defaults for unknown values. */
18
45
  declare function parseConfig(text: string): FlaghoistConfig;
19
46
  /** Render a config back to `flaghoist.toml` text. */
20
47
  declare function serializeConfig(config: FlaghoistConfig): string;
21
48
 
22
- export { type AdminAuthKind, DEFAULT_CONFIG, type FlaghoistConfig, STORAGE_KINDS, type StorageKind, parseConfig, serializeConfig };
49
+ export { type AdminAuthKind, type ContainerStorage, DEFAULT_CONFIG, type FlaghoistConfig, PLATFORM_KINDS, type PlatformKind, STORAGE_KINDS, type StorageKind, asContainer, containerStorageDefault, parseConfig, serializeConfig };
package/dist/lib.js CHANGED
@@ -1,12 +1,18 @@
1
1
  import {
2
2
  DEFAULT_CONFIG,
3
+ PLATFORM_KINDS,
3
4
  STORAGE_KINDS,
5
+ asContainer,
6
+ containerStorageDefault,
4
7
  parseConfig,
5
8
  serializeConfig
6
- } from "./chunk-S4XXPMMW.js";
9
+ } from "./chunk-HSDDPXCM.js";
7
10
  export {
8
11
  DEFAULT_CONFIG,
12
+ PLATFORM_KINDS,
9
13
  STORAGE_KINDS,
14
+ asContainer,
15
+ containerStorageDefault,
10
16
  parseConfig,
11
17
  serializeConfig
12
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flaghoist",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Scaffold, deploy, and manage your Flaghoist feature-flag service from the terminal.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -23,12 +23,13 @@
23
23
  "node": ">=20"
24
24
  },
25
25
  "dependencies": {
26
- "smol-toml": "^1.8.0"
26
+ "smol-toml": "^1.8.0",
27
+ "@flaghoist/admin-client": "0.2.0"
27
28
  },
28
29
  "devDependencies": {
29
- "@flaghoist/core": "0.1.2",
30
30
  "@flaghoist/adapter-memory": "0.1.2",
31
- "@flaghoist/server": "0.3.0"
31
+ "@flaghoist/core": "0.1.2",
32
+ "@flaghoist/server": "0.3.1"
32
33
  },
33
34
  "publishConfig": {
34
35
  "access": "public"