@arkstack/common 0.12.35 → 0.12.37

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/dist/index.d.ts CHANGED
@@ -275,6 +275,15 @@ declare const nodeEnv: () => "prod" | "dev";
275
275
  * @returns
276
276
  */
277
277
  declare const outputDir: (cwd?: string) => string;
278
+ /**
279
+ * Rebuild the application output (tsdown) into {@link outputDir}, wiping it first
280
+ * so no stale emitted modules survive a source change. Standalone — it does NOT
281
+ * boot the app — so the console kernel can call it to self-heal a stale or
282
+ * incomplete build artifact that would otherwise wedge startup. Build-only: it
283
+ * sets `CLI_BUILD` so tsdown emits without starting a watcher/dev server, and
284
+ * inherits the current `NODE_ENV` so it targets the same dir the kernel reads.
285
+ */
286
+ declare const rebuildOutput: () => Promise<void>;
278
287
  /**
279
288
  *
280
289
  * Dynamically imports a file at the given path with full TypeScript support,
@@ -287,6 +296,22 @@ declare const outputDir: (cwd?: string) => string;
287
296
  * const config = await importFile<AppConfig>('./config/app.ts')
288
297
  */
289
298
  declare const importFile: FileImporter;
299
+ /**
300
+ * Discover console command classes from the application's command directory.
301
+ *
302
+ * Commands are loaded straight from TypeScript source through {@link importFile}
303
+ * (jiti), so they are picked up without a build and reflect edits on every run.
304
+ * The built output is only used as a fallback when the source directory is
305
+ * absent — e.g. a production deploy that ships `dist` without `src`.
306
+ *
307
+ * This exists because musket's own glob discovery imports paths with native
308
+ * `import()`, which silently skips `.ts` files — the reason commands previously
309
+ * only appeared after a `build --dev` and never reflected later edits.
310
+ *
311
+ * @param subPath Command directory relative to the app root (src/dist aware).
312
+ * @returns The discovered command classes.
313
+ */
314
+ declare const discoverCommands: <T = unknown>(subPath?: string) => Promise<T[]>;
290
315
  /**
291
316
  * Resolves the default export from a module, handling both CJS and ESM interop.
292
317
  * In CJS modules, the default export is often the module itself (a function or object),
@@ -432,4 +457,4 @@ declare class Hook {
432
457
  static clear: () => void;
433
458
  }
434
459
  //#endregion
435
- export { AbstractModelConstructor, AppException, ArkstackErrorPayload, ArkstackErrorShape, CONFIG_KEY, ConfigRegistry, ConfigShape, DotPath, DotPathValue, Encryption, EnvRegistry, ErrorHandler, Exception, FileImporter, GlobalConfig, GlobalEnv, Hash, Hook, HookArgs, HookFor, HookName, HookPos, HookPositions, HookRegistry, IHook, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, MergedConfig, ModelConstructor, ModelRegistry, Primitive, RequestException, UnionToIntersection, abort, abortIf, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, renderError, serializeError, shouldHideStack, shouldLogError, toErrorShape };
460
+ export { AbstractModelConstructor, AppException, ArkstackErrorPayload, ArkstackErrorShape, CONFIG_KEY, ConfigRegistry, ConfigShape, DotPath, DotPathValue, Encryption, EnvRegistry, ErrorHandler, Exception, FileImporter, GlobalConfig, GlobalEnv, Hash, Hook, HookArgs, HookFor, HookName, HookPos, HookPositions, HookRegistry, IHook, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, MergedConfig, ModelConstructor, ModelRegistry, Primitive, RequestException, UnionToIntersection, abort, abortIf, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, discoverCommands, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, serializeError, shouldHideStack, shouldLogError, toErrorShape };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as importFile, E as outputDir, S as env, T as nodeEnv, _ as Hash, b as appUrl, c as abortIf, d as initializeGlobalContext, f as isClass, g as Exception, h as AppException, l as assertFound, m as RequestException, p as perPage, s as abort, u as getModel, v as Encryption, w as interopDefault, x as config, y as CONFIG_KEY } from "./utils-CFjlZZ91.js";
1
+ import { C as env, D as outputDir, E as nodeEnv, O as rebuildOutput, S as discoverCommands, T as interopDefault, _ as Hash, b as appUrl, c as abortIf, d as initializeGlobalContext, f as isClass, g as Exception, h as AppException, l as assertFound, m as RequestException, p as perPage, s as abort, u as getModel, v as Encryption, w as importFile, x as config, y as CONFIG_KEY } from "./utils-yPBhsll_.js";
2
2
  import { Hook as Hook$1 } from "@arkstack/foundry";
3
3
  import { str } from "@h3ravel/support";
4
4
  import { Arkstack } from "@arkstack/contract";
@@ -481,4 +481,4 @@ var Hook = class {
481
481
  };
482
482
  };
483
483
  //#endregion
484
- export { AppException, CONFIG_KEY, Encryption, ErrorHandler, Exception, Hash, Hook, Logger, RequestException, abort, abortIf, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, renderError, serializeError, shouldHideStack, shouldLogError, toErrorShape };
484
+ export { AppException, CONFIG_KEY, Encryption, ErrorHandler, Exception, Hash, Hook, Logger, RequestException, abort, abortIf, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, discoverCommands, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, serializeError, shouldHideStack, shouldLogError, toErrorShape };
@@ -1,2 +1,2 @@
1
- import { _ as Hash, a as use, c as abortIf, d as initializeGlobalContext, f as isClass, i as trait, l as assertFound, n as crc32, o as uses, p as perPage, r as getTraitMethods, s as abort, t as callTraitMethods, u as getModel, v as Encryption } from "../utils-CFjlZZ91.js";
1
+ import { _ as Hash, a as use, c as abortIf, d as initializeGlobalContext, f as isClass, i as trait, l as assertFound, n as crc32, o as uses, p as perPage, r as getTraitMethods, s as abort, t as callTraitMethods, u as getModel, v as Encryption } from "../utils-yPBhsll_.js";
2
2
  export { Encryption, Hash, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getTraitMethods, initializeGlobalContext, isClass, perPage, trait, use, uses };
@@ -6,6 +6,8 @@ import { createRequire } from "module";
6
6
  import path, { resolve } from "node:path";
7
7
  import { pathToFileURL } from "node:url";
8
8
  import { readdirSync } from "fs";
9
+ import { rm } from "node:fs/promises";
10
+ import { spawn } from "node:child_process";
9
11
  import { Secret, TOTP } from "otpauth";
10
12
  import { compare, genSalt, hash } from "bcryptjs";
11
13
  import { getUserConfig } from "arkormx";
@@ -121,6 +123,40 @@ const outputDir = (cwd) => {
121
123
  return path.isAbsolute(output[NODE_ENV] ?? output.dev) ? output[NODE_ENV] ?? output.dev : path.join(cwd, output[NODE_ENV] ?? output.dev);
122
124
  };
123
125
  /**
126
+ * Rebuild the application output (tsdown) into {@link outputDir}, wiping it first
127
+ * so no stale emitted modules survive a source change. Standalone — it does NOT
128
+ * boot the app — so the console kernel can call it to self-heal a stale or
129
+ * incomplete build artifact that would otherwise wedge startup. Build-only: it
130
+ * sets `CLI_BUILD` so tsdown emits without starting a watcher/dev server, and
131
+ * inherits the current `NODE_ENV` so it targets the same dir the kernel reads.
132
+ */
133
+ const rebuildOutput = async () => {
134
+ await rm(outputDir(), {
135
+ recursive: true,
136
+ force: true
137
+ });
138
+ await new Promise((resolveBuild, reject) => {
139
+ const child = spawn(process.platform === "win32" ? "pnpm.cmd" : "pnpm", [
140
+ "exec",
141
+ "tsdown",
142
+ "--log-level",
143
+ "silent"
144
+ ], {
145
+ cwd: Arkstack.rootDir(),
146
+ stdio: "inherit",
147
+ env: Object.assign({}, process.env, { CLI_BUILD: "true" })
148
+ });
149
+ child.on("error", reject);
150
+ child.on("exit", (code) => {
151
+ if (code === 0 || code === null) {
152
+ resolveBuild();
153
+ return;
154
+ }
155
+ reject(/* @__PURE__ */ new Error(`tsdown exited with code ${code}`));
156
+ });
157
+ });
158
+ };
159
+ /**
124
160
  *
125
161
  * Dynamically imports a file at the given path with full TypeScript support,
126
162
  * including `tsconfig.json` path aliases.
@@ -140,6 +176,67 @@ const importFile = async (filePath, userOptions, resolveOptions) => {
140
176
  }).import(resolvedPath, resolveOptions);
141
177
  };
142
178
  /**
179
+ * Picks the command class out of an imported module. Prefers the export named
180
+ * after the file (musket's discovery convention), then a default export, then
181
+ * the first exported constructor it finds.
182
+ *
183
+ * @param mod The imported module namespace.
184
+ * @param basename The file name without extension.
185
+ * @returns The resolved command class, or undefined when none is found.
186
+ */
187
+ const resolveCommandExport = (mod, basename) => {
188
+ const named = mod[basename];
189
+ if (typeof named === "function") return named;
190
+ if (typeof mod.default === "function") return mod.default;
191
+ return Object.values(mod).find((value) => typeof value === "function");
192
+ };
193
+ /**
194
+ * Discover console command classes from the application's command directory.
195
+ *
196
+ * Commands are loaded straight from TypeScript source through {@link importFile}
197
+ * (jiti), so they are picked up without a build and reflect edits on every run.
198
+ * The built output is only used as a fallback when the source directory is
199
+ * absent — e.g. a production deploy that ships `dist` without `src`.
200
+ *
201
+ * This exists because musket's own glob discovery imports paths with native
202
+ * `import()`, which silently skips `.ts` files — the reason commands previously
203
+ * only appeared after a `build --dev` and never reflected later edits.
204
+ *
205
+ * @param subPath Command directory relative to the app root (src/dist aware).
206
+ * @returns The discovered command classes.
207
+ */
208
+ const discoverCommands = async (subPath = path.join("app", "console", "commands")) => {
209
+ const root = Arkstack.rootDir();
210
+ const dist = path.relative(root, outputDir());
211
+ const candidateDirs = [path.join(root, "src", subPath), path.join(root, dist, subPath)];
212
+ let commandsDir;
213
+ let files = [];
214
+ for (const dir of candidateDirs) try {
215
+ const entries = readdirSync(dir, { withFileTypes: true }).filter((file) => file.isFile() && [
216
+ ".ts",
217
+ ".js",
218
+ ".mjs"
219
+ ].includes(path.extname(file.name)));
220
+ if (entries.length > 0) {
221
+ commandsDir = dir;
222
+ files = entries;
223
+ break;
224
+ }
225
+ } catch {}
226
+ if (!commandsDir) return [];
227
+ const commands = [];
228
+ for (const file of files) {
229
+ const basename = path.basename(file.name, path.extname(file.name));
230
+ try {
231
+ const command = resolveCommandExport(await importFile(path.join(commandsDir, file.name)), basename);
232
+ if (command) commands.push(command);
233
+ } catch (error) {
234
+ console.error(`[arkstack] Failed to load command "${file.name}":`, error);
235
+ }
236
+ }
237
+ return commands;
238
+ };
239
+ /**
143
240
  * Resolves the default export from a module, handling both CJS and ESM interop.
144
241
  * In CJS modules, the default export is often the module itself (a function or object),
145
242
  * while in ESM the default is nested under the `default` property.
@@ -580,4 +677,4 @@ function uses(instance, trait) {
580
677
  return false;
581
678
  }
582
679
  //#endregion
583
- export { importFile as C, outputDir as E, env as S, nodeEnv as T, Hash as _, use as a, appUrl as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, assertFound as l, RequestException as m, crc32 as n, uses as o, perPage as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel as u, Encryption as v, interopDefault as w, config as x, CONFIG_KEY as y };
680
+ export { env as C, outputDir as D, nodeEnv as E, rebuildOutput as O, discoverCommands as S, interopDefault as T, Hash as _, use as a, appUrl as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, assertFound as l, RequestException as m, crc32 as n, uses as o, perPage as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel as u, Encryption as v, importFile as w, config as x, CONFIG_KEY as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/common",
3
- "version": "0.12.35",
3
+ "version": "0.12.37",
4
4
  "type": "module",
5
5
  "description": "Core utilities, primitives, and shared infrastructure for the Arkstack ecosystem.",
6
6
  "homepage": "https://arkstack.toneflix.net",
@@ -42,8 +42,8 @@
42
42
  "peerDependencies": {
43
43
  "@h3ravel/support": "^2.1.4",
44
44
  "arkormx": "^2.9.0",
45
- "@arkstack/foundry": "^0.12.35",
46
- "@arkstack/contract": "^0.12.35"
45
+ "@arkstack/foundry": "^0.12.37",
46
+ "@arkstack/contract": "^0.12.37"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsdown --config-loader unrun",