create-flaghoist 0.2.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +93 -44
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { PLATFORM_KINDS as PLATFORM_KINDS2, STORAGE_KINDS as STORAGE_KINDS2 } from "flaghoist";
5
- import { parseArgs } from "util";
4
+ import { PLATFORM_KINDS as PLATFORM_KINDS2, setupSummary, STORAGE_KINDS as STORAGE_KINDS2, terminalAsker } from "flaghoist";
6
5
  import { relative } from "path";
6
+ import { parseArgs } from "util";
7
7
 
8
8
  // src/scaffold.ts
9
9
  import {
10
- asContainer,
11
- DEFAULT_CONFIG,
10
+ collectSetup,
11
+ defaultsAsker,
12
12
  PLATFORM_KINDS,
13
- serializeConfig,
14
- STORAGE_KINDS
13
+ STORAGE_KINDS,
14
+ writeSetup
15
15
  } from "flaghoist";
16
- import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
17
- import { join, resolve } from "path";
18
- function scaffold(options = {}) {
16
+ import { existsSync, mkdirSync, readdirSync } from "fs";
17
+ import { basename, join, resolve } from "path";
18
+ async function scaffold(options = {}) {
19
19
  const root = options.cwd ?? process.cwd();
20
20
  if (options.storage && !STORAGE_KINDS.includes(options.storage)) {
21
21
  throw new Error(`Unknown storage "${options.storage}". One of: ${STORAGE_KINDS.join(", ")}.`);
@@ -23,72 +23,121 @@ function scaffold(options = {}) {
23
23
  if (options.platform && !PLATFORM_KINDS.includes(options.platform)) {
24
24
  throw new Error(`Unknown platform "${options.platform}". One of: ${PLATFORM_KINDS.join(", ")}.`);
25
25
  }
26
- const dir = options.directory ? resolve(root, options.directory) : resolve(root);
26
+ if (options.directory) assertUsable(resolve(root, options.directory), options.directory);
27
+ else if (!options.asker?.interactive) assertUsable(resolve(root), void 0);
28
+ const { directory, cwd: _cwd, asker, storage, platform, ...rest } = options;
29
+ const preset = {
30
+ ...rest,
31
+ storage,
32
+ platform,
33
+ // The directory name is the natural project name.
34
+ name: rest.name ?? (directory ? basename(directory) : void 0)
35
+ };
36
+ const setup = await collectSetup(asker ?? defaultsAsker(), preset);
37
+ const target = directory ?? (asker?.interactive === false || !asker ? void 0 : setup.config.name);
38
+ const dir = target ? resolve(root, target) : resolve(root);
39
+ assertUsable(dir, target);
27
40
  const existed = existsSync(dir);
28
- if (existed && readdirSync(dir).length > 0) {
41
+ if (!existed) mkdirSync(dir, { recursive: true });
42
+ const { envPath } = writeSetup(dir, setup);
43
+ return {
44
+ dir,
45
+ configPath: join(dir, "flaghoist.toml"),
46
+ ...envPath ? { envPath } : {},
47
+ createdDirectory: !existed,
48
+ config: setup.config,
49
+ setup
50
+ };
51
+ }
52
+ function assertUsable(dir, shown) {
53
+ if (existsSync(dir) && readdirSync(dir).length > 0) {
29
54
  throw new Error(
30
- options.directory ? `Directory "${options.directory}" already exists and is not empty.` : "The current directory is not empty. Pass a directory name, e.g. `npm create flaghoist team-flags`."
55
+ shown ? `Directory "${shown}" already exists and is not empty.` : "The current directory is not empty. Pass a directory name, e.g. `npm create flaghoist team-flags`."
31
56
  );
32
57
  }
33
- if (!existed) mkdirSync(dir, { recursive: true });
34
- const base = {
35
- ...DEFAULT_CONFIG,
36
- // The directory name is the natural project name; falling back keeps `.` usable.
37
- name: options.directory ?? DEFAULT_CONFIG.name,
38
- storage: options.storage ?? DEFAULT_CONFIG.storage
39
- };
40
- const config = options.platform === "container" ? asContainer(base) : base;
41
- const configPath = join(dir, "flaghoist.toml");
42
- writeFileSync(configPath, serializeConfig(config));
43
- return { dir, configPath, createdDirectory: !existed, config };
44
58
  }
45
59
 
46
60
  // src/index.ts
47
61
  function printHelp() {
48
- console.log(`create-flaghoist \u2014 scaffold a Flaghoist feature-flag service
62
+ console.log(`create-flaghoist: set up a Flaghoist feature-flag service
49
63
 
50
64
  Usage:
51
- npm create flaghoist@latest <directory> [--storage <kind>] [--platform <kind>]
65
+ npm create flaghoist@latest [directory] -- [options]
66
+
67
+ With no options it asks a few questions. Any option below answers its question up front.
52
68
 
53
69
  Options:
54
- --storage One of: ${STORAGE_KINDS2.join(", ")} (default: ${STORAGE_KINDS2[0]})
55
- --platform One of: ${PLATFORM_KINDS2.join(", ")} (default: ${PLATFORM_KINDS2[0]})
56
- -h, --help Show this message
70
+ --name <name> Project name (default: the directory, or team-flags)
71
+ --platform <kind> One of: ${PLATFORM_KINDS2.join(", ")}
72
+ --storage <kind> One of: ${STORAGE_KINDS2.join(", ")}
73
+ --no-accounts Keep the single admin token, without accounts and roles
74
+ --sso-issuer <url> Turn on SSO with this OpenID Connect issuer
75
+ --sso-client-id <id> The SSO client id
76
+ --sso-admin-group <name> Members of this group become admins
77
+ --no-dashboard Do not serve the dashboard at /admin
78
+ -y, --yes Take the defaults for everything not given
79
+ -h, --help Show this message
57
80
 
58
- Omit <directory> to scaffold into the current directory (it must be empty).`);
81
+ Omit [directory] to be asked for a project name; it becomes the directory.`);
59
82
  }
60
- function main() {
83
+ async function main() {
61
84
  const { values, positionals } = parseArgs({
62
85
  args: process.argv.slice(2),
63
86
  allowPositionals: true,
64
87
  options: {
88
+ name: { type: "string" },
65
89
  storage: { type: "string" },
66
90
  platform: { type: "string" },
91
+ "no-accounts": { type: "boolean" },
92
+ "sso-issuer": { type: "string" },
93
+ "sso-client-id": { type: "string" },
94
+ "sso-admin-group": { type: "string" },
95
+ "no-dashboard": { type: "boolean" },
96
+ yes: { type: "boolean", short: "y" },
67
97
  help: { type: "boolean", short: "h" }
68
98
  }
69
99
  });
70
100
  if (values.help) return printHelp();
71
- const { dir, configPath, config } = scaffold({
101
+ const interactive = process.stdin.isTTY === true && values.yes !== true;
102
+ const asker = interactive ? terminalAsker() : void 0;
103
+ if (interactive) console.log("\nLet us set up your Flaghoist service.\n");
104
+ const options = {
72
105
  directory: positionals[0],
106
+ name: values.name,
73
107
  storage: values.storage,
74
- platform: values.platform
75
- });
76
- const shown = relative(process.cwd(), configPath) || "flaghoist.toml";
108
+ platform: values.platform,
109
+ ...values["no-accounts"] ? { accounts: false } : {},
110
+ ...values["no-dashboard"] ? { dashboard: false } : {},
111
+ ...values["sso-issuer"] ? { ssoIssuer: values["sso-issuer"] } : {},
112
+ ...values["sso-client-id"] ? { ssoClientId: values["sso-client-id"] } : {},
113
+ ...values["sso-admin-group"] ? { ssoAdminGroup: values["sso-admin-group"] } : {},
114
+ asker
115
+ };
116
+ let result;
117
+ try {
118
+ result = await scaffold(options);
119
+ } finally {
120
+ asker?.close();
121
+ }
122
+ const shown = relative(process.cwd(), result.configPath) || "flaghoist.toml";
77
123
  console.log(`
78
124
  Created ${shown}`);
79
- console.log(` name ${config.name}`);
80
- console.log(` storage ${config.storage}`);
81
- console.log(` platform ${config.platform}
82
- `);
83
- console.log("That file is the entire project. Next:\n");
84
- const cd = relative(process.cwd(), dir);
125
+ const summary = setupSummary(
126
+ interactive ? result.setup : { ...result.setup, pepperGenerated: false },
127
+ result.envPath !== void 0
128
+ );
129
+ for (const line of summary) console.log(line);
130
+ console.log("\nNext:\n");
131
+ const cd = relative(process.cwd(), result.dir);
85
132
  if (cd) console.log(` cd ${cd}`);
86
133
  console.log(" npx flaghoist deploy\n");
134
+ if (result.config.accounts?.enabled) {
135
+ console.log("After deploying, open /admin and sign in with your ADMIN_TOKEN once to create the");
136
+ console.log("owner account. Everyone after that is invited from the Members page.\n");
137
+ }
87
138
  console.log("Prefer to own the code? `npx flaghoist eject` turns it into a project you edit.");
88
139
  }
89
- try {
90
- main();
91
- } catch (error) {
140
+ main().catch((error) => {
92
141
  console.error(error instanceof Error ? error.message : String(error));
93
142
  process.exit(1);
94
- }
143
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-flaghoist",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Scaffold a Flaghoist feature-flag service — the package behind `npm create flaghoist`.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  "scaffold"
22
22
  ],
23
23
  "dependencies": {
24
- "flaghoist": "0.4.0"
24
+ "flaghoist": "0.5.1"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"