@m6d/cortex-cli 0.13.0 → 0.15.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.
@@ -0,0 +1,41 @@
1
+ {
2
+ "version": "0.2",
3
+ "language": "en",
4
+ "words": ["mprocs"],
5
+ "ignorePaths": [
6
+ "node_modules/**",
7
+ "out/**",
8
+ "build/**",
9
+ "**/dist/**",
10
+ "coverage/**",
11
+ "*.tsbuildinfo",
12
+ "bun.lock",
13
+ "package-lock.json",
14
+ "pnpm-lock.yaml",
15
+ "yarn.lock",
16
+ "*.db",
17
+ "*.sqlite",
18
+ "scratch.*",
19
+ ".env*",
20
+ "**/*.min.js",
21
+ "**/*.min.css",
22
+ "**/db/migrations/**",
23
+ "**/i18n/**",
24
+ "docs",
25
+ ".claude",
26
+ ".angular",
27
+ ".vercel/**",
28
+ "vendor/**",
29
+ ".gitignore"
30
+ ],
31
+ "dictionaries": [
32
+ "typescript",
33
+ "node",
34
+ "npm",
35
+ "html",
36
+ "css",
37
+ "bash",
38
+ "companies",
39
+ "softwareTerms"
40
+ ]
41
+ }
@@ -1,7 +1,6 @@
1
- # The env files never go into an image: they hold this checkout's secrets.
2
- # With their example copies left out too, setup.ts writes none during the
3
- # image's own `bun install`.
1
+ # .env never goes into an image: it holds this checkout's secrets. With
2
+ # .env.example left out too, setup.ts writes none during the image's own
3
+ # `bun install`.
4
4
  .env*
5
- cortex.env*
6
5
  node_modules/
7
6
  .git/
@@ -1,9 +1,8 @@
1
1
  # Dependencies
2
2
  node_modules/
3
3
 
4
- # Environment variables: the .example files are the committed ones
4
+ # Environment variables: .env.example is the committed one
5
5
  .env
6
- cortex.env
7
6
  .env.local
8
7
  .env.*.local
9
8
 
@@ -2,14 +2,13 @@ import { randomBytes } from "node:crypto";
2
2
  import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
 
4
4
  /*
5
- * Writes .env and cortex.env on every `bun install`: each starts from its
6
- * committed .example copy, and each value the copy leaves blank is
7
- * generated here, the Compose project this checkout's stack runs as among
8
- * them. A value already set is never changed, so a fresh clone gets a stack
9
- * and secrets of its own, and a rerun changes nothing. The one exception: a
10
- * cortex.env written now holds new keys, which cannot read the volumes of the
11
- * stack .env names, so the stack gets a new name too. Without the example
12
- * copies, as in an image build, it writes nothing.
5
+ * Writes .env on every `bun install`: it starts from the committed
6
+ * .env.example, or from .env when there is one, and each value left blank
7
+ * that is listed below is generated here, the Compose project this
8
+ * checkout's stack runs as among them. A value already set is never
9
+ * changed, so a fresh clone gets a stack and secrets of its own, and a rerun
10
+ * changes nothing. Without either file, as in an image build, it writes
11
+ * nothing.
13
12
  */
14
13
 
15
14
  const token = () => randomBytes(32).toString("base64url");
@@ -24,29 +23,22 @@ const project =
24
23
  .replace(/^-+|-+$/g, "") || "cortex";
25
24
 
26
25
  const GENERATED = [
27
- [".env", "COMPOSE_PROJECT_NAME", () => `${project}-${randomBytes(4).toString("hex")}`],
28
- [".env", "CORTEX_SERVER_KEY", token],
29
- ["cortex.env", "DEFAULT_ADMIN_PASSWORD", token],
26
+ ["COMPOSE_PROJECT_NAME", () => `${project}-${randomBytes(4).toString("hex")}`],
27
+ ["CORTEX_SERVER_KEY", token],
28
+ ["DEFAULT_ADMIN_PASSWORD", token],
30
29
  // Cortex takes management keys only under this prefix. cspell:ignore mgmt
31
- ["cortex.env", "BOOTSTRAP_MANAGEMENT_KEY", () => `cortex_mgmt_${token()}`],
32
- ["cortex.env", "BETTER_AUTH_SECRET", key],
33
- ["cortex.env", "SECRETS_MASTER_KEY", key],
30
+ ["BOOTSTRAP_MANAGEMENT_KEY", () => `cortex_mgmt_${token()}`],
31
+ ["BETTER_AUTH_SECRET", key],
32
+ ["SECRETS_MASTER_KEY", key],
34
33
  ] as const;
35
34
 
36
- const freshKeys = !existsSync("cortex.env") && existsSync("cortex.env.example");
37
-
38
- for (const file of [".env", "cortex.env"]) {
39
- const source = existsSync(file) ? file : `${file}.example`;
40
- if (!existsSync(source)) continue;
35
+ const source = existsSync(".env") ? ".env" : ".env.example";
36
+ if (existsSync(source)) {
41
37
  // Only its owner reads a file of secrets, however it was first created.
42
- if (source === file) chmodSync(file, 0o600);
38
+ if (source === ".env") chmodSync(".env", 0o600);
43
39
  let text = readFileSync(source, "utf8");
44
- if (file === ".env" && freshKeys) {
45
- text = text.replace(/^COMPOSE_PROJECT_NAME=.*$/m, "COMPOSE_PROJECT_NAME=");
46
- }
47
- for (const [target, variable, generate] of GENERATED) {
48
- if (target !== file) continue;
40
+ for (const [variable, generate] of GENERATED) {
49
41
  text = text.replace(new RegExp(`^${variable}=$`, "m"), `${variable}=${generate()}`);
50
42
  }
51
- writeFileSync(file, text, { mode: 0o600 });
43
+ writeFileSync(".env", text, { mode: 0o600 });
52
44
  }
package/src/ui/prompts.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  log,
15
15
  outro,
16
16
  password,
17
+ select,
17
18
  spinner,
18
19
  text,
19
20
  } from "@clack/prompts";
@@ -50,29 +51,50 @@ export function askName(check: (name: string) => string | undefined) {
50
51
  );
51
52
  }
52
53
 
53
- /** A yes-or-no question a flag left open, answered no unless the user says otherwise. */
54
- export function askConfirm(message: string) {
55
- return orExit(confirm({ message, initialValue: false }));
54
+ /** One of `options`, by its value; the first is the default. */
55
+ export function askChoice<const TValue extends string>(
56
+ message: string,
57
+ options: Parameters<typeof select<TValue>>[0]["options"],
58
+ ) {
59
+ return orExit(select({ message, options }));
56
60
  }
57
61
 
58
- /** A required value a command was not given: asked by name, re-asked while empty. */
59
- export function askValue(name: string, hint?: string) {
62
+ /** A yes-or-no question a flag left open, answered no unless the caller says otherwise. */
63
+ export function askConfirm(message: string, initialValue = false) {
64
+ return orExit(confirm({ message, initialValue }));
65
+ }
66
+
67
+ /**
68
+ * A required value a command was not given: asked by name, re-asked while
69
+ * empty or while `check` returns a complaint about the trimmed value.
70
+ */
71
+ export function askValue(
72
+ name: string,
73
+ hint?: string,
74
+ check: (value: string) => string | undefined = () => undefined,
75
+ ) {
60
76
  return orExit(
61
77
  text({
62
78
  message: name,
63
79
  placeholder: hint,
64
- validate: (value) =>
65
- (value ?? "").trim().length === 0 ? `${name} is required.` : undefined,
80
+ validate: function (value) {
81
+ const candidate = (value ?? "").trim();
82
+ return candidate.length === 0 ? `${name} is required.` : check(candidate);
83
+ },
66
84
  }),
67
85
  );
68
86
  }
69
87
 
70
- /** A credential: typed masked, never echoed. */
71
- export function askSecret(name: string) {
88
+ /** A credential: typed masked, never echoed, and re-asked while `check` complains. */
89
+ export function askSecret(
90
+ name: string,
91
+ check: (value: string) => string | undefined = () => undefined,
92
+ ) {
72
93
  return orExit(
73
94
  password({
74
95
  message: name,
75
- validate: (value) => ((value ?? "").length === 0 ? `${name} is required.` : undefined),
96
+ validate: (value) =>
97
+ (value ?? "").length === 0 ? `${name} is required.` : check(value ?? ""),
76
98
  }),
77
99
  );
78
100
  }