@arkstack/console 0.14.18 → 0.14.20

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.
@@ -1,4 +1,4 @@
1
- import { readdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { Arkstack } from "@arkstack/contract";
4
4
  import { Node, Project } from "ts-morph";
@@ -61,6 +61,113 @@ var BuildInterfaces = class BuildInterfaces {
61
61
  };
62
62
  for (const [file, config] of Object.entries(configs)) writeFileSync(path.join(Arkstack.rootDir(), file), config, "utf8");
63
63
  }
64
+ /**
65
+ * Generate an `EnvRegistry` augmentation from the application's `.env`
66
+ * schema, giving `env()` precise return types for app-specific variables.
67
+ *
68
+ * Mirrors {@link configs}: the declaration is appended to
69
+ * `.arkstack/ark.d.ts`. Keys already typed by the framework's own
70
+ * `EnvRegistry` are skipped so declaration merging never conflicts.
71
+ *
72
+ * @param envFile Explicit `.env` path; defaults to `.env.example` then `.env`.
73
+ */
74
+ static env(envFile) {
75
+ const root = Arkstack.rootDir();
76
+ const file = envFile ?? BuildInterfaces.resolveEnvFile(root);
77
+ if (!file) return;
78
+ const declaration = BuildInterfaces.envRegistryFromEnv(readFileSync(file, "utf8"), [...BuildInterfaces.frameworkEnvKeys()]);
79
+ if (!declaration) return;
80
+ const target = path.join(root, ".arkstack/ark.d.ts");
81
+ let content = `${existsSync(target) ? readFileSync(target, "utf8") : ""}\n${declaration}\n`;
82
+ if (!/^\s*export\s|\bimport\s/m.test(content)) content += "\nexport {}\n";
83
+ writeFileSync(target, content, "utf8");
84
+ }
85
+ /**
86
+ * Render an `EnvRegistry` augmentation for the variables declared in the
87
+ * given `.env` contents. Pure (no filesystem) for testability.
88
+ *
89
+ * @param contents Raw `.env` file contents.
90
+ * @param skip Variable names to omit (e.g. framework-owned keys).
91
+ * @returns The `declare module` block, or `''` when nothing to emit.
92
+ */
93
+ static envRegistryFromEnv(contents, skip = []) {
94
+ const skipped = new Set(skip);
95
+ const properties = Object.entries(BuildInterfaces.parseEnvFile(contents)).filter(([key]) => !skipped.has(key)).map(([key, value]) => ` ${key}: ${BuildInterfaces.inferEnvType(value)}`);
96
+ if (!properties.length) return "";
97
+ return [
98
+ "declare module '@arkstack/common' {",
99
+ " interface EnvRegistry {",
100
+ ...properties,
101
+ " }",
102
+ "}"
103
+ ].join("\n");
104
+ }
105
+ /**
106
+ * Prefer `.env.example` (the documented schema), then `.env`.
107
+ *
108
+ * @param root
109
+ * @returns
110
+ */
111
+ static resolveEnvFile(root) {
112
+ for (const name of [".env.example", ".env"]) {
113
+ const file = path.join(root, name);
114
+ if (existsSync(file)) return file;
115
+ }
116
+ }
117
+ /**
118
+ * Parse `KEY=VALUE` lines, skipping blanks, comments and invalid names.
119
+ *
120
+ * @param contents
121
+ * @returns
122
+ */
123
+ static parseEnvFile(contents) {
124
+ const entries = {};
125
+ for (const raw of contents.split(/\r?\n/)) {
126
+ const line = raw.trim();
127
+ if (!line || line.startsWith("#")) continue;
128
+ const eq = line.indexOf("=");
129
+ if (eq === -1) continue;
130
+ const key = line.slice(0, eq).trim();
131
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
132
+ let value = line.slice(eq + 1).trim();
133
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
134
+ entries[key] = value;
135
+ }
136
+ return entries;
137
+ }
138
+ /**
139
+ * Infer a TS type from a value, mirroring env()'s runtime coercion.
140
+ *
141
+ * @param value
142
+ * @returns
143
+ */
144
+ static inferEnvType(value) {
145
+ if (value === "") return "string";
146
+ if ([
147
+ "true",
148
+ "false",
149
+ "on",
150
+ "off"
151
+ ].includes(value)) return "boolean";
152
+ if (!Number.isNaN(Number(value))) return "number";
153
+ return "string";
154
+ }
155
+ /**
156
+ * Resolve the framework's own `EnvRegistry` keys to skip during generation.
157
+ *
158
+ * @returns
159
+ */
160
+ static frameworkEnvKeys() {
161
+ try {
162
+ const keys = new Project({
163
+ tsConfigFilePath: path.join(Arkstack.rootDir(), "tsconfig.json"),
164
+ skipAddingFilesFromTsConfig: true
165
+ }).createSourceFile("__ark_env_probe__.ts", "import type { EnvRegistry } from '@arkstack/common'\ndeclare const value: EnvRegistry\n", { overwrite: true }).getVariableDeclarationOrThrow("value").getType().getProperties().map((prop) => prop.getName());
166
+ return new Set(keys);
167
+ } catch {
168
+ return /* @__PURE__ */ new Set();
169
+ }
170
+ }
64
171
  static generateConfig(configDir = path.join(process.cwd(), "src/config")) {
65
172
  BuildInterfaces.project = new Project({
66
173
  tsConfigFilePath: path.join(process.cwd(), "tsconfig.json"),
package/dist/index.d.ts CHANGED
@@ -9,6 +9,53 @@ declare class BuildInterfaces {
9
9
  */
10
10
  static configs(configDir?: string): void;
11
11
  static tsconfig(): void;
12
+ /**
13
+ * Generate an `EnvRegistry` augmentation from the application's `.env`
14
+ * schema, giving `env()` precise return types for app-specific variables.
15
+ *
16
+ * Mirrors {@link configs}: the declaration is appended to
17
+ * `.arkstack/ark.d.ts`. Keys already typed by the framework's own
18
+ * `EnvRegistry` are skipped so declaration merging never conflicts.
19
+ *
20
+ * @param envFile Explicit `.env` path; defaults to `.env.example` then `.env`.
21
+ */
22
+ static env(envFile?: string): void;
23
+ /**
24
+ * Render an `EnvRegistry` augmentation for the variables declared in the
25
+ * given `.env` contents. Pure (no filesystem) for testability.
26
+ *
27
+ * @param contents Raw `.env` file contents.
28
+ * @param skip Variable names to omit (e.g. framework-owned keys).
29
+ * @returns The `declare module` block, or `''` when nothing to emit.
30
+ */
31
+ static envRegistryFromEnv(contents: string, skip?: string[]): string;
32
+ /**
33
+ * Prefer `.env.example` (the documented schema), then `.env`.
34
+ *
35
+ * @param root
36
+ * @returns
37
+ */
38
+ private static resolveEnvFile;
39
+ /**
40
+ * Parse `KEY=VALUE` lines, skipping blanks, comments and invalid names.
41
+ *
42
+ * @param contents
43
+ * @returns
44
+ */
45
+ private static parseEnvFile;
46
+ /**
47
+ * Infer a TS type from a value, mirroring env()'s runtime coercion.
48
+ *
49
+ * @param value
50
+ * @returns
51
+ */
52
+ private static inferEnvType;
53
+ /**
54
+ * Resolve the framework's own `EnvRegistry` keys to skip during generation.
55
+ *
56
+ * @returns
57
+ */
58
+ private static frameworkEnvKeys;
12
59
  private static generateConfig;
13
60
  private static resolveReturnTypeAnnotation;
14
61
  private static findNamedTypeImport;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as ArkstackConsoleApp } from "./app-DnaQWE9q.js";
3
- import { n as BaseTCConfig, r as TSConfig, t as BuildInterfaces } from "./BuildInterfaces-CsZ3Uerb.js";
3
+ import { n as BaseTCConfig, r as TSConfig, t as BuildInterfaces } from "./BuildInterfaces-CPmSl41C.js";
4
4
  import { Publisher, abort, abortIf, assertFound, config, discoverCommands, env, importFile, initializeGlobalContext, loadPrototypes, outputDir, rebuildOutput } from "@arkstack/common";
5
5
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
package/dist/prepare.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as BuildInterfaces } from "./BuildInterfaces-CsZ3Uerb.js";
2
+ import { t as BuildInterfaces } from "./BuildInterfaces-CPmSl41C.js";
3
3
  import { existsSync, mkdirSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { Arkstack } from "@arkstack/contract";
@@ -8,7 +8,10 @@ import chalk from "chalk";
8
8
 
9
9
  //#region dist/prepare.js
10
10
  if (!existsSync(path.join(Arkstack.rootDir(), ".arkstack/build"))) mkdirSync(path.join(Arkstack.rootDir(), ".arkstack/build"), { recursive: true });
11
- if (!process.env.NODE_CI) BuildInterfaces.configs();
11
+ if (!process.env.NODE_CI) {
12
+ BuildInterfaces.configs();
13
+ BuildInterfaces.env();
14
+ }
12
15
  BuildInterfaces.tsconfig();
13
16
  const LOG_LEVEL = parseInt(process.env.VERBOSITY ?? "0") > 0 ? [] : ["--log-level=silent"];
14
17
  const NODE_ENV = process.env.NODE_ENV || "development";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/console",
3
- "version": "0.14.18",
3
+ "version": "0.14.20",
4
4
  "type": "module",
5
5
  "description": "Console module for Arkstack, providing the command-line runtime and console integration layer.",
6
6
  "homepage": "https://arkstack.toneflix.net/guide/cli",
@@ -51,8 +51,8 @@
51
51
  "chalk": "^5.6.2",
52
52
  "resora": "^1.3.27",
53
53
  "ts-morph": "^28.0.0",
54
- "@arkstack/common": "^0.14.18",
55
- "@arkstack/contract": "^0.14.18"
54
+ "@arkstack/common": "^0.14.20",
55
+ "@arkstack/contract": "^0.14.20"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsdown",