flaghoist 0.3.1 → 0.5.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/README.md +14 -1
- package/dist/chunk-TQ5S6NOA.js +391 -0
- package/dist/index.js +709 -165
- package/dist/lib.d.ts +86 -1
- package/dist/lib.js +21 -3
- package/package.json +5 -5
- package/dist/chunk-HSDDPXCM.js +0 -67
package/dist/lib.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Readable, Writable } from 'node:stream';
|
|
2
|
+
|
|
1
3
|
type StorageKind = 'cloudflare-kv' | 'redis' | 'postgres' | 'sqlite' | 'memory';
|
|
2
4
|
type AdminAuthKind = 'bearer-token' | 'oidc';
|
|
3
5
|
/**
|
|
@@ -7,6 +9,21 @@ type AdminAuthKind = 'bearer-token' | 'oidc';
|
|
|
7
9
|
* this key existed still scaffolds a Worker.
|
|
8
10
|
*/
|
|
9
11
|
type PlatformKind = 'cloudflare' | 'container';
|
|
12
|
+
/** Single sign-on settings kept in `flaghoist.toml`. The client secret is never stored here. */
|
|
13
|
+
interface SsoSettings {
|
|
14
|
+
issuer: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
/** Members of this identity-provider group become admins. Everyone else signs in as a viewer. */
|
|
17
|
+
adminGroup?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* User accounts. Only the choices live in `flaghoist.toml`; the secrets (`AUTH_PEPPER`, and
|
|
21
|
+
* `SSO_CLIENT_SECRET` with SSO) come from the environment, so the file stays safe to commit.
|
|
22
|
+
*/
|
|
23
|
+
interface AccountsSettings {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
sso?: SsoSettings;
|
|
26
|
+
}
|
|
10
27
|
interface FlaghoistConfig {
|
|
11
28
|
name: string;
|
|
12
29
|
storage: StorageKind;
|
|
@@ -19,6 +36,8 @@ interface FlaghoistConfig {
|
|
|
19
36
|
allowedOrigins?: string[];
|
|
20
37
|
/** Serve the admin dashboard at `/admin`. On by default. */
|
|
21
38
|
dashboard: boolean;
|
|
39
|
+
/** Absent means off, so a config written before accounts existed behaves as it always did. */
|
|
40
|
+
accounts?: AccountsSettings;
|
|
22
41
|
}
|
|
23
42
|
declare const DEFAULT_CONFIG: FlaghoistConfig;
|
|
24
43
|
/** Every storage backend selectable by name in `flaghoist.toml`. */
|
|
@@ -46,4 +65,70 @@ declare function parseConfig(text: string): FlaghoistConfig;
|
|
|
46
65
|
/** Render a config back to `flaghoist.toml` text. */
|
|
47
66
|
declare function serializeConfig(config: FlaghoistConfig): string;
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The interactive setup behind `npm create flaghoist` and `flaghoist init`. It asks for the choices
|
|
70
|
+
* that shape a project, and writes them to `flaghoist.toml`. Secrets (the accounts pepper and an SSO
|
|
71
|
+
* client secret) never go in that file: they go in a git-ignored `.env`, and `flaghoist deploy`
|
|
72
|
+
* hands them to Cloudflare as Worker secrets.
|
|
73
|
+
*/
|
|
74
|
+
/** Everything the wizard asks through, so tests can script the answers. */
|
|
75
|
+
interface Asker {
|
|
76
|
+
text(question: string, fallback: string): Promise<string>;
|
|
77
|
+
choice<T extends string>(question: string, options: Choice<T>[], fallback: T): Promise<T>;
|
|
78
|
+
yesNo(question: string, fallback: boolean): Promise<boolean>;
|
|
79
|
+
secret(question: string): Promise<string>;
|
|
80
|
+
say(line: string): void;
|
|
81
|
+
/** False when nobody can answer, so a required value that is missing is an error, not a loop. */
|
|
82
|
+
interactive?: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface Choice<T extends string> {
|
|
85
|
+
value: T;
|
|
86
|
+
label: string;
|
|
87
|
+
hint?: string;
|
|
88
|
+
}
|
|
89
|
+
/** Answers given up front on the command line. Each one skips its question. */
|
|
90
|
+
interface SetupPreset {
|
|
91
|
+
name?: string;
|
|
92
|
+
platform?: PlatformKind;
|
|
93
|
+
storage?: StorageKind;
|
|
94
|
+
accounts?: boolean;
|
|
95
|
+
dashboard?: boolean;
|
|
96
|
+
sso?: boolean;
|
|
97
|
+
ssoIssuer?: string;
|
|
98
|
+
ssoClientId?: string;
|
|
99
|
+
ssoAdminGroup?: string;
|
|
100
|
+
}
|
|
101
|
+
interface SetupResult {
|
|
102
|
+
config: FlaghoistConfig;
|
|
103
|
+
/** Secrets for `.env`: never written to `flaghoist.toml`. */
|
|
104
|
+
secrets: Record<string, string>;
|
|
105
|
+
/** True when the pepper was generated here rather than pasted in. */
|
|
106
|
+
pepperGenerated: boolean;
|
|
107
|
+
}
|
|
108
|
+
declare const MIN_PEPPER_LENGTH = 32;
|
|
109
|
+
/** The stores each platform can reach. Cloudflare KV only exists on Workers; SQLite needs a disk. */
|
|
110
|
+
declare function storageChoices(platform: PlatformKind): Choice<StorageKind>[];
|
|
111
|
+
declare function generatePepper(): string;
|
|
112
|
+
/** Ask every question the preset does not already answer, and build the project from the answers. */
|
|
113
|
+
declare function collectSetup(asker: Asker, preset?: SetupPreset): Promise<SetupResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Write `flaghoist.toml`, and when there are secrets, a `.env` holding them plus a `.gitignore` that
|
|
116
|
+
* keeps it out of git. An existing `.gitignore` gets `.env` appended rather than replaced.
|
|
117
|
+
*/
|
|
118
|
+
declare function writeSetup(dir: string, result: SetupResult): {
|
|
119
|
+
envPath?: string;
|
|
120
|
+
};
|
|
121
|
+
/** Read `KEY=value` lines from a `.env` file. Comments and blank lines are skipped. */
|
|
122
|
+
declare function readEnvFile(path: string): Record<string, string>;
|
|
123
|
+
/** What to tell the person at the end, including the pepper once if it was generated. */
|
|
124
|
+
declare function setupSummary(result: SetupResult, envWritten: boolean): string[];
|
|
125
|
+
/** Ask on a real terminal. Choices are numbered; Enter takes the default shown in brackets. */
|
|
126
|
+
declare function terminalAsker(input?: Readable & {
|
|
127
|
+
isTTY?: boolean;
|
|
128
|
+
}, output?: Writable): Asker & {
|
|
129
|
+
close(): void;
|
|
130
|
+
};
|
|
131
|
+
/** The asker used without a terminal: every question takes its default, and nothing is printed. */
|
|
132
|
+
declare function defaultsAsker(): Asker;
|
|
133
|
+
|
|
134
|
+
export { type AccountsSettings, type AdminAuthKind, type Asker, type Choice, type ContainerStorage, DEFAULT_CONFIG, type FlaghoistConfig, MIN_PEPPER_LENGTH, PLATFORM_KINDS, type PlatformKind, STORAGE_KINDS, type SetupPreset, type SetupResult, type SsoSettings, type StorageKind, asContainer, collectSetup, containerStorageDefault, defaultsAsker, generatePepper, parseConfig, readEnvFile, serializeConfig, setupSummary, storageChoices, terminalAsker, writeSetup };
|
package/dist/lib.js
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_CONFIG,
|
|
3
|
+
MIN_PEPPER_LENGTH,
|
|
3
4
|
PLATFORM_KINDS,
|
|
4
5
|
STORAGE_KINDS,
|
|
5
6
|
asContainer,
|
|
7
|
+
collectSetup,
|
|
6
8
|
containerStorageDefault,
|
|
9
|
+
defaultsAsker,
|
|
10
|
+
generatePepper,
|
|
7
11
|
parseConfig,
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
readEnvFile,
|
|
13
|
+
serializeConfig,
|
|
14
|
+
setupSummary,
|
|
15
|
+
storageChoices,
|
|
16
|
+
terminalAsker,
|
|
17
|
+
writeSetup
|
|
18
|
+
} from "./chunk-TQ5S6NOA.js";
|
|
10
19
|
export {
|
|
11
20
|
DEFAULT_CONFIG,
|
|
21
|
+
MIN_PEPPER_LENGTH,
|
|
12
22
|
PLATFORM_KINDS,
|
|
13
23
|
STORAGE_KINDS,
|
|
14
24
|
asContainer,
|
|
25
|
+
collectSetup,
|
|
15
26
|
containerStorageDefault,
|
|
27
|
+
defaultsAsker,
|
|
28
|
+
generatePepper,
|
|
16
29
|
parseConfig,
|
|
17
|
-
|
|
30
|
+
readEnvFile,
|
|
31
|
+
serializeConfig,
|
|
32
|
+
setupSummary,
|
|
33
|
+
storageChoices,
|
|
34
|
+
terminalAsker,
|
|
35
|
+
writeSetup
|
|
18
36
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flaghoist",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"smol-toml": "^1.8.0",
|
|
27
|
-
"@flaghoist/admin-client": "0.
|
|
27
|
+
"@flaghoist/admin-client": "0.3.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@flaghoist/
|
|
31
|
-
"@flaghoist/
|
|
32
|
-
"@flaghoist/
|
|
30
|
+
"@flaghoist/core": "0.2.0",
|
|
31
|
+
"@flaghoist/server": "0.4.0",
|
|
32
|
+
"@flaghoist/adapter-memory": "0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
package/dist/chunk-HSDDPXCM.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// src/config.ts
|
|
2
|
-
import { parse } from "smol-toml";
|
|
3
|
-
var DEFAULT_CONFIG = {
|
|
4
|
-
name: "team-flags",
|
|
5
|
-
storage: "cloudflare-kv",
|
|
6
|
-
platform: "cloudflare",
|
|
7
|
-
auth: { admin: "bearer-token", read: "api-key" },
|
|
8
|
-
dashboard: true
|
|
9
|
-
};
|
|
10
|
-
var STORAGE_KINDS = [
|
|
11
|
-
"cloudflare-kv",
|
|
12
|
-
"redis",
|
|
13
|
-
"postgres",
|
|
14
|
-
"sqlite",
|
|
15
|
-
"memory"
|
|
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
|
-
}
|
|
24
|
-
function parseConfig(text) {
|
|
25
|
-
const raw = parse(text);
|
|
26
|
-
const name = typeof raw.name === "string" && raw.name ? raw.name : DEFAULT_CONFIG.name;
|
|
27
|
-
const storage = STORAGE_KINDS.includes(raw.storage) ? raw.storage : DEFAULT_CONFIG.storage;
|
|
28
|
-
const platform = raw.platform === "container" ? "container" : DEFAULT_CONFIG.platform;
|
|
29
|
-
const authRaw = typeof raw.auth === "object" && raw.auth !== null ? raw.auth : {};
|
|
30
|
-
const admin = authRaw.admin === "oidc" ? "oidc" : "bearer-token";
|
|
31
|
-
const allowedOrigins = Array.isArray(raw.allowedOrigins) ? raw.allowedOrigins.filter((x) => typeof x === "string") : void 0;
|
|
32
|
-
const dashboard = raw.dashboard !== false;
|
|
33
|
-
return { name, storage, platform, auth: { admin, read: "api-key" }, allowedOrigins, dashboard };
|
|
34
|
-
}
|
|
35
|
-
function serializeConfig(config) {
|
|
36
|
-
const lines = [
|
|
37
|
-
`name = ${JSON.stringify(config.name)}`,
|
|
38
|
-
`storage = ${JSON.stringify(config.storage)}`
|
|
39
|
-
];
|
|
40
|
-
if (config.platform !== "cloudflare") {
|
|
41
|
-
lines.push(`platform = ${JSON.stringify(config.platform)}`);
|
|
42
|
-
}
|
|
43
|
-
if (config.allowedOrigins && config.allowedOrigins.length > 0) {
|
|
44
|
-
lines.push(`allowedOrigins = ${JSON.stringify(config.allowedOrigins)}`);
|
|
45
|
-
}
|
|
46
|
-
if (!config.dashboard) {
|
|
47
|
-
lines.push("dashboard = false");
|
|
48
|
-
}
|
|
49
|
-
lines.push(
|
|
50
|
-
"",
|
|
51
|
-
"[auth]",
|
|
52
|
-
`admin = ${JSON.stringify(config.auth.admin)}`,
|
|
53
|
-
`read = ${JSON.stringify(config.auth.read)}`
|
|
54
|
-
);
|
|
55
|
-
return `${lines.join("\n")}
|
|
56
|
-
`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export {
|
|
60
|
-
DEFAULT_CONFIG,
|
|
61
|
-
STORAGE_KINDS,
|
|
62
|
-
PLATFORM_KINDS,
|
|
63
|
-
containerStorageDefault,
|
|
64
|
-
asContainer,
|
|
65
|
-
parseConfig,
|
|
66
|
-
serializeConfig
|
|
67
|
-
};
|