@arkstack/common 0.14.15 → 0.14.17

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
@@ -324,6 +324,50 @@ declare const nodeEnv: () => "prod" | "dev";
324
324
  * @returns
325
325
  */
326
326
  declare const outputDir: (cwd?: string) => string;
327
+ /**
328
+ * Map an application source path to its build-output counterpart.
329
+ *
330
+ * Application code is authored under `src/` and compiled into {@link outputDir},
331
+ * which strips the leading `src/` segment and emits JavaScript (e.g.
332
+ * `src/app/models/User.ts` -> `dist/app/models/User.js`). A TypeScript source
333
+ * extension is rewritten to `.js`; paths without one (directories) keep their
334
+ * shape. Absolute or root-relative paths outside the app root are returned
335
+ * unchanged.
336
+ *
337
+ * This is a pure path transform — it does not touch the filesystem. Use
338
+ * {@link resolveRuntimeModule} / {@link resolveRuntimeDir} when you need an
339
+ * existing file/dir for the current environment.
340
+ *
341
+ * @param sourcePath Absolute or root-relative source path.
342
+ */
343
+ declare const toOutputPath: (sourcePath: string) => string;
344
+ /**
345
+ * Resolve an application module's source path to a file that can be imported at
346
+ * runtime.
347
+ *
348
+ * In development the TypeScript source is loaded directly (jiti compiles on the
349
+ * fly); in production only the build output ships, so the path is remapped into
350
+ * {@link outputDir} with a compiled extension. The first existing candidate
351
+ * wins — production prefers the build output, development prefers source — so a
352
+ * deploy that ships only `dist` never reaches for `src`.
353
+ *
354
+ * @param sourcePath Absolute or root-relative source path, with or without extension.
355
+ * @returns An existing importable path, or `sourcePath` unchanged when none exists.
356
+ */
357
+ declare const resolveRuntimeModule: (sourcePath: string) => string;
358
+ /**
359
+ * Resolve an application source directory to the directory that exists at
360
+ * runtime.
361
+ *
362
+ * The directory counterpart of {@link resolveRuntimeModule}: it maps the source
363
+ * directory into {@link outputDir} (stripping the leading `src/` segment) but
364
+ * appends no file extension. Production prefers the build output, development
365
+ * prefers source, and the absolute source path is returned when neither exists.
366
+ *
367
+ * @param sourcePath Absolute or root-relative source directory.
368
+ * @returns An existing directory path, or the absolute source path when none exists.
369
+ */
370
+ declare const resolveRuntimeDir: (sourcePath: string) => string;
327
371
  /**
328
372
  * Rebuild the application output (tsdown) into {@link outputDir}, wiping it first
329
373
  * so no stale emitted modules survive a source change. Standalone — it does NOT
@@ -545,4 +589,4 @@ declare class Hook {
545
589
  static clear: () => void;
546
590
  }
547
591
  //#endregion
548
- 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, PublishEntry, PublishFilter, PublishGroup, Publisher, RequestException, UnionToIntersection, abort, abortIf, appKey, 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 };
592
+ 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, PublishEntry, PublishFilter, PublishGroup, Publisher, RequestException, UnionToIntersection, abort, abortIf, appKey, 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, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as discoverCommands, D as nodeEnv, E as interopDefault, O as outputDir, S as config, T as importFile, _ as Hash, b as appKey, c as abortIf, d as initializeGlobalContext, f as isClass, g as Exception, h as AppException, k as rebuildOutput, l as assertFound, m as RequestException, p as perPage, s as abort, u as getModel, v as Encryption, w as env, x as appUrl, y as CONFIG_KEY } from "./utils-CL_FD72V.js";
1
+ import { A as resolveRuntimeDir, C as discoverCommands, D as nodeEnv, E as interopDefault, M as toOutputPath, O as outputDir, S as config, T as importFile, _ as Hash, b as appKey, c as abortIf, d as initializeGlobalContext, f as isClass, g as Exception, h as AppException, j as resolveRuntimeModule, k as rebuildOutput, l as assertFound, m as RequestException, p as perPage, s as abort, u as getModel, v as Encryption, w as env, x as appUrl, y as CONFIG_KEY } from "./utils-DJQAOLbx.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";
@@ -529,4 +529,4 @@ var Hook = class {
529
529
  };
530
530
  };
531
531
  //#endregion
532
- export { AppException, CONFIG_KEY, Encryption, ErrorHandler, Exception, Hash, Hook, Logger, Publisher, RequestException, abort, abortIf, appKey, 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 };
532
+ export { AppException, CONFIG_KEY, Encryption, ErrorHandler, Exception, Hash, Hook, Logger, Publisher, RequestException, abort, abortIf, appKey, 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, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
@@ -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-CL_FD72V.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-DJQAOLbx.js";
2
2
  export { Encryption, Hash, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getTraitMethods, initializeGlobalContext, isClass, perPage, trait, use, uses };
@@ -1,11 +1,11 @@
1
1
  import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
2
2
  import { Arr, Obj, undot } from "@h3ravel/support";
3
3
  import { createJiti } from "jiti";
4
+ import { existsSync, readdirSync } from "fs";
4
5
  import { Arkstack } from "@arkstack/contract";
5
6
  import { createRequire } from "module";
6
7
  import path, { resolve } from "node:path";
7
8
  import { pathToFileURL } from "node:url";
8
- import { readdirSync } from "fs";
9
9
  import { rm } from "node:fs/promises";
10
10
  import { spawn } from "node:child_process";
11
11
  import { Secret, TOTP } from "otpauth";
@@ -147,6 +147,103 @@ const outputDir = (cwd) => {
147
147
  };
148
148
  return path.isAbsolute(output[NODE_ENV] ?? output.dev) ? output[NODE_ENV] ?? output.dev : path.join(cwd, output[NODE_ENV] ?? output.dev);
149
149
  };
150
+ const SOURCE_DIR = "src";
151
+ const SOURCE_EXTENSIONS = [
152
+ ".ts",
153
+ ".tsx",
154
+ ".mts",
155
+ ".cts",
156
+ ".js",
157
+ ".mjs",
158
+ ".cjs"
159
+ ];
160
+ const OUTPUT_EXTENSIONS = [
161
+ ".js",
162
+ ".mjs",
163
+ ".cjs",
164
+ ".ts"
165
+ ];
166
+ /**
167
+ * Strip a trailing known source/compiled extension from a path.
168
+ *
169
+ * @param value
170
+ * @returns
171
+ */
172
+ const stripKnownExtension = (value) => value.replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "");
173
+ /**
174
+ * Build the list of concrete file candidates for a base path. Any existing
175
+ * source/compiled extension is dropped first so the correct runtime extension
176
+ * can be tried (e.g. a `.ts` source maps to a `.js` candidate under `dist`).
177
+ *
178
+ * @param base
179
+ * @param extensions
180
+ * @returns
181
+ */
182
+ const moduleCandidates = (base, extensions) => {
183
+ const bare = stripKnownExtension(base);
184
+ return extensions.map((ext) => bare + ext);
185
+ };
186
+ /**
187
+ * Map an application source path to its build-output counterpart.
188
+ *
189
+ * Application code is authored under `src/` and compiled into {@link outputDir},
190
+ * which strips the leading `src/` segment and emits JavaScript (e.g.
191
+ * `src/app/models/User.ts` -> `dist/app/models/User.js`). A TypeScript source
192
+ * extension is rewritten to `.js`; paths without one (directories) keep their
193
+ * shape. Absolute or root-relative paths outside the app root are returned
194
+ * unchanged.
195
+ *
196
+ * This is a pure path transform — it does not touch the filesystem. Use
197
+ * {@link resolveRuntimeModule} / {@link resolveRuntimeDir} when you need an
198
+ * existing file/dir for the current environment.
199
+ *
200
+ * @param sourcePath Absolute or root-relative source path.
201
+ */
202
+ const toOutputPath = (sourcePath) => {
203
+ const root = Arkstack.rootDir();
204
+ const abs = path.isAbsolute(sourcePath) ? sourcePath : path.join(root, sourcePath);
205
+ const rel = path.relative(root, abs);
206
+ if (!rel || rel.startsWith("..")) return abs;
207
+ return path.join(outputDir(), rel.replace(new RegExp(`^${SOURCE_DIR}[\\\\/]`), "")).replace(/\.(ts|tsx|mts|cts)$/i, ".js");
208
+ };
209
+ /**
210
+ * Resolve an application module's source path to a file that can be imported at
211
+ * runtime.
212
+ *
213
+ * In development the TypeScript source is loaded directly (jiti compiles on the
214
+ * fly); in production only the build output ships, so the path is remapped into
215
+ * {@link outputDir} with a compiled extension. The first existing candidate
216
+ * wins — production prefers the build output, development prefers source — so a
217
+ * deploy that ships only `dist` never reaches for `src`.
218
+ *
219
+ * @param sourcePath Absolute or root-relative source path, with or without extension.
220
+ * @returns An existing importable path, or `sourcePath` unchanged when none exists.
221
+ */
222
+ const resolveRuntimeModule = (sourcePath) => {
223
+ const root = Arkstack.rootDir();
224
+ const abs = path.isAbsolute(sourcePath) ? sourcePath : path.join(root, sourcePath);
225
+ const sourceCandidates = moduleCandidates(abs, SOURCE_EXTENSIONS);
226
+ const outputCandidates = moduleCandidates(toOutputPath(abs), OUTPUT_EXTENSIONS);
227
+ return (nodeEnv() === "prod" ? [...outputCandidates, ...sourceCandidates] : [...sourceCandidates, ...outputCandidates]).find((candidate) => existsSync(candidate)) ?? abs;
228
+ };
229
+ /**
230
+ * Resolve an application source directory to the directory that exists at
231
+ * runtime.
232
+ *
233
+ * The directory counterpart of {@link resolveRuntimeModule}: it maps the source
234
+ * directory into {@link outputDir} (stripping the leading `src/` segment) but
235
+ * appends no file extension. Production prefers the build output, development
236
+ * prefers source, and the absolute source path is returned when neither exists.
237
+ *
238
+ * @param sourcePath Absolute or root-relative source directory.
239
+ * @returns An existing directory path, or the absolute source path when none exists.
240
+ */
241
+ const resolveRuntimeDir = (sourcePath) => {
242
+ const root = Arkstack.rootDir();
243
+ const abs = path.isAbsolute(sourcePath) ? sourcePath : path.join(root, sourcePath);
244
+ const mapped = toOutputPath(abs);
245
+ return (nodeEnv() === "prod" ? [mapped, abs] : [abs, mapped]).find((candidate) => existsSync(candidate)) ?? abs;
246
+ };
150
247
  /**
151
248
  * Rebuild the application output (tsdown) into {@link outputDir}, wiping it first
152
249
  * so no stale emitted modules survive a source change. Standalone — it does NOT
@@ -232,8 +329,9 @@ const resolveCommandExport = (mod, basename) => {
232
329
  */
233
330
  const discoverCommands = async (subPath = path.join("app", "console", "commands")) => {
234
331
  const root = Arkstack.rootDir();
235
- const dist = path.relative(root, outputDir());
236
- const candidateDirs = [path.join(root, "src", subPath), path.join(root, dist, subPath)];
332
+ const sourceDir = path.join(root, SOURCE_DIR, subPath);
333
+ const outputCommandDir = path.join(outputDir(), subPath);
334
+ const candidateDirs = nodeEnv() === "prod" ? [outputCommandDir, sourceDir] : [sourceDir, outputCommandDir];
237
335
  let commandsDir;
238
336
  let files = [];
239
337
  for (const dir of candidateDirs) try {
@@ -440,7 +538,7 @@ async function getModel(modelName) {
440
538
  };
441
539
  const isModelModule = (value) => typeof value === "object" && value !== null;
442
540
  const modelPath = getUserConfig().paths?.models || "./src/models";
443
- const model = resolveModelExport(await importFile(path.join(path.isAbsolute(modelPath) ? modelPath : path.join(Arkstack.rootDir(), modelPath), modelName)), path.basename(modelName, path.extname(modelName)));
541
+ const model = resolveModelExport(await importFile(resolveRuntimeModule(path.join(path.isAbsolute(modelPath) ? modelPath : path.join(Arkstack.rootDir(), modelPath), modelName))), path.basename(modelName, path.extname(modelName)));
444
542
  if (typeof model !== "function") throw new Error(`Model "${modelName}" not found`);
445
543
  return model;
446
544
  }
@@ -702,4 +800,4 @@ function uses(instance, trait) {
702
800
  return false;
703
801
  }
704
802
  //#endregion
705
- export { discoverCommands as C, nodeEnv as D, interopDefault as E, outputDir as O, config as S, importFile as T, Hash as _, use as a, appKey as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, rebuildOutput as k, 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, env as w, appUrl as x, CONFIG_KEY as y };
803
+ export { resolveRuntimeDir as A, discoverCommands as C, nodeEnv as D, interopDefault as E, toOutputPath as M, outputDir as O, config as S, importFile as T, Hash as _, use as a, appKey as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, resolveRuntimeModule as j, rebuildOutput as k, 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, env as w, appUrl as x, CONFIG_KEY as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/common",
3
- "version": "0.14.15",
3
+ "version": "0.14.17",
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.3",
45
- "@arkstack/foundry": "^0.14.15",
46
- "@arkstack/contract": "^0.14.15"
45
+ "@arkstack/foundry": "^0.14.17",
46
+ "@arkstack/contract": "^0.14.17"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsdown --config-loader unrun",