@crustjs/core 0.3.1 → 0.3.2

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.js CHANGED
@@ -1,4 +1,5 @@
1
- import { C as normalizeFlag, S as normalizeArg, _ as defineExtension, a as runInvocation, b as definingOf, d as resolveArtifactDir, f as cloneCommandNode, g as registerFlag, h as createCommandNode, i as resolveTypedPath, m as validateCommandSections, n as executeInvocation, o as snapshotCommand, p as installExtensionContexts, r as prepareInvocation, v as contextSources, w as CrustError, x as validateContextAvailability, y as defineContext } from "./invocation-CIvHShiW.js";
1
+ import { S as CrustError, _ as defineContext, a as runInvocation, b as normalizeArg, d as installExtensionContexts, f as validateCommandSections, g as contextSources, h as defineExtension, i as resolveTypedPath, m as registerFlag, n as executeInvocation, o as snapshotCommand, p as createCommandNode, r as prepareInvocation, u as cloneCommandNode, v as definingOf, x as normalizeFlag, y as validateContextAvailability } from "./invocation-B17RvTLb.js";
2
+ import { resolveArtifactDir } from "@crustjs/utils/artifacts";
2
3
  //#region src/identity.ts
3
4
  /** Mint an Extension identity from any non-blank, trimmed string. */
4
5
  function defineExtensionId(id) {
@@ -1,12 +1,12 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join, posix, resolve, win32 } from "node:path";
3
- import { existsSync, realpathSync, statSync } from "node:fs";
4
- import { fileURLToPath } from "node:url";
5
- import { AsyncLocalStorage } from "node:async_hooks";
6
- import { Writable } from "node:stream";
3
+ import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
4
+ import { withAmbientTerminalIO } from "@crustjs/utils/terminal";
7
5
  import { parseArgs } from "node:util";
8
6
  import { isPromise } from "node:util/types";
7
+ import { coerceBooleanString, tryCoerceNumber } from "@crustjs/utils/primitive";
9
8
  import { homedir } from "node:os";
9
+ import { normalizeStandardIssues } from "@crustjs/utils/schema";
10
10
  //#region src/errors.ts
11
11
  /**
12
12
  * A typed error for runtime recipe, Extension, Context, documentation, argv, and validation failures.
@@ -614,156 +614,6 @@ function installExtensionContexts(node, extensions, reRegisteredIds) {
614
614
  return cloned;
615
615
  }
616
616
  //#endregion
617
- //#region ../utils/src/source.ts
618
- /**
619
- * Find the nearest directory containing `package.json` by walking up the
620
- * filesystem from `startPath`.
621
- *
622
- * If `startPath` points at an existing file, its parent directory is used as
623
- * the starting point. The directory walk uses `path.resolve()` (lexical), not
624
- * `fs.realpath()`, so a symlink's parent chain is walked rather than the
625
- * symlink target's parent chain.
626
- *
627
- * @param startPath - Directory or file path to start walking from.
628
- * @returns The absolute path of the nearest enclosing directory containing
629
- * `package.json`, or `null` if the filesystem root is reached first.
630
- *
631
- * @internal Shared by {@link resolveSourceDir} and `resolveArtifactDir`.
632
- */
633
- function findNearestPackageRoot(startPath) {
634
- let current = resolve(startPath);
635
- if (existsSync(current) && !statSync(current).isDirectory()) current = dirname(current);
636
- while (true) {
637
- if (existsSync(join(current, "package.json"))) return current;
638
- const parent = dirname(current);
639
- if (parent === current) return null;
640
- current = parent;
641
- }
642
- }
643
- //#endregion
644
- //#region ../utils/src/artifacts.ts
645
- /**
646
- * Set by `crust build` while it prepares the Command Snapshot: the absolute
647
- * entry-isolated directory Extension build hooks write into. Core reads it
648
- * to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
649
- * that run see the artifacts being built instead of the wiped `.crust/root`.
650
- */
651
- const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
652
- /** True inside a `bun build --compile` or `deno compile` executable. */
653
- function isCompiledExecutable() {
654
- const { Bun, Deno } = globalThis;
655
- const bunMain = Bun?.main ?? "";
656
- return bunMain.startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/.test(bunMain) || Deno?.build?.standalone === true;
657
- }
658
- /**
659
- * Absolute path of a build artifact or `crust.include` directory shipped with
660
- * this CLI. `name` is a top-level directory name such as `"skills"`.
661
- *
662
- * The artifact path is computed from how the CLI is running — never probed:
663
- * - Compiled executable (Bun or Deno): `<dir of the executable>/<name>`, which
664
- * is a platform package's `bin/` or wherever the binary was placed.
665
- * - Crust-built Node bundle: `<name>` next to the bundle's `bin/` directory,
666
- * i.e. `.crust/root/<name>` in place and `<installed root>/<name>` after install.
667
- * - Snapshot preparation inside `crust build`: `<build output dir>/<name>`, the
668
- * artifacts earlier Extension build hooks wrote in this same build.
669
- * - Source (`bun run`, `node`, `deno run`): `.crust/root/<name>` under the
670
- * nearest package root of the real `process.argv[1]` entrypoint (following source
671
- * links) — the output of the last `crust build`.
672
- *
673
- * @throws {Error} when `name` is not a single path segment, or in source mode
674
- * when the entrypoint cannot be resolved or has no enclosing `package.json`.
675
- */
676
- function resolveArtifactDir(name) {
677
- if (name === "" || name === "." || name === ".." || /[\\/]/.test(name)) throw new Error(`Artifact name must be a single directory name, got ${JSON.stringify(name)}.`);
678
- if (isCompiledExecutable()) return join(dirname(process.execPath), name);
679
- if (process.env.CRUST_INTERNAL_BUILD === "1") return resolve(fileURLToPath(import.meta.url), "..", "..", name);
680
- const buildOutDir = process.env[BUILD_OUT_DIR_ENV];
681
- if (buildOutDir) return join(buildOutDir, name);
682
- const entrypoint = process.argv[1];
683
- let sourceEntrypoint = entrypoint;
684
- if (entrypoint) try {
685
- sourceEntrypoint = realpathSync(entrypoint);
686
- } catch (cause) {
687
- throw new Error(`Could not resolve artifact "${name}": could not resolve source entrypoint "${entrypoint}".`, { cause });
688
- }
689
- const packageRoot = sourceEntrypoint ? findNearestPackageRoot(sourceEntrypoint) : null;
690
- if (!packageRoot) throw new Error(`Could not resolve artifact "${name}": no package.json was found above ${entrypoint ? `entrypoint "${entrypoint}"` : "process.argv[1] (unset)"}.`);
691
- return join(packageRoot, ".crust", "root", name);
692
- }
693
- //#endregion
694
- //#region ../utils/src/terminal.ts
695
- const STORAGE_KEY = Symbol.for("crustjs.terminal.io");
696
- const AMBIENT_CALLBACKS_KEY = Symbol.for("crustjs.terminal.ambient-callbacks");
697
- const globalWithStorage = globalThis;
698
- const globalWithAmbientCallbacks = globalThis;
699
- /** Bundled copies in Core, Prompts, and Progress share process-wide terminal state. */
700
- const storage = globalWithStorage[STORAGE_KEY] ??= new AsyncLocalStorage();
701
- const ambientCallbacks = globalWithAmbientCallbacks[AMBIENT_CALLBACKS_KEY] ??= /* @__PURE__ */ new WeakMap();
702
- /** Run a function with terminal streams available in its async scope. */
703
- function withTerminalIO(io, fn) {
704
- const current = storage.getStore();
705
- return storage.run({
706
- input: io.input ?? current?.input,
707
- output: io.output ?? current?.output
708
- }, fn);
709
- }
710
- function lineBufferedOutput(io) {
711
- let pending = "";
712
- const output = new Writable({
713
- decodeStrings: false,
714
- write(chunk, _encoding, callback) {
715
- pending += chunk.toString();
716
- let newline = pending.indexOf("\n");
717
- while (newline !== -1) {
718
- io.stderr(pending.slice(0, newline));
719
- pending = pending.slice(newline + 1);
720
- newline = pending.indexOf("\n");
721
- }
722
- callback();
723
- }
724
- });
725
- ambientCallbacks.set(output, io);
726
- return output;
727
- }
728
- /** Run a function with Core's line-oriented output bridged into the terminal stream scope. */
729
- function withAmbientTerminalIO(io, fn) {
730
- const current = storage.getStore();
731
- return withTerminalIO({
732
- input: current?.input,
733
- output: current?.output && !ambientCallbacks.has(current.output) ? current.output : lineBufferedOutput(io)
734
- }, fn);
735
- }
736
- //#endregion
737
- //#region ../utils/src/primitive.ts
738
- /**
739
- * Attempts to coerce a string to a number, returning `undefined` only when the
740
- * result is `NaN`. Callers decide whether `undefined` means throw or fallback.
741
- *
742
- * @example
743
- * ```ts
744
- * tryCoerceNumber("42"); // 42
745
- * tryCoerceNumber("abc"); // undefined
746
- * tryCoerceNumber(""); // 0
747
- * ```
748
- */
749
- function tryCoerceNumber(raw) {
750
- const num = Number(raw);
751
- return Number.isNaN(num) ? void 0 : num;
752
- }
753
- /**
754
- * Coerces Crust boolean strings using the existing strict truthy spellings.
755
- *
756
- * @example
757
- * ```ts
758
- * coerceBooleanString("true"); // true
759
- * coerceBooleanString("1"); // true
760
- * coerceBooleanString("false"); // false
761
- * ```
762
- */
763
- function coerceBooleanString(raw) {
764
- return raw === "true" || raw === "1";
765
- }
766
- //#endregion
767
617
  //#region src/parsing/coercers.ts
768
618
  /**
769
619
  * Coerce a raw argv string into a {@link URL} instance via the WHATWG
@@ -1203,67 +1053,6 @@ function validateParsed(command, parsed) {
1203
1053
  validateRequiredFlags(flagsDef, flags);
1204
1054
  }
1205
1055
  //#endregion
1206
- //#region ../utils/src/schema.ts
1207
- /**
1208
- * Format an issue path into a dot-path string.
1209
- *
1210
- * - Numeric keys (array indexes) are rendered with bracket notation: `items[0]`
1211
- * - String/symbol keys are joined with dots: `flags.verbose`
1212
- * - An empty path array produces an empty string (root-level issue)
1213
- *
1214
- * @example
1215
- * ```ts
1216
- * formatPath(["flags", "verbose"]);
1217
- * // => "flags.verbose"
1218
- *
1219
- * formatPath(["args", 0]);
1220
- * // => "args[0]"
1221
- *
1222
- * formatPath([]);
1223
- * // => ""
1224
- * ```
1225
- */
1226
- function isNumericPathSegment(segment) {
1227
- return typeof segment === "number";
1228
- }
1229
- function formatPath(path) {
1230
- return path.map((segment, index) => isNumericPathSegment(segment) ? `[${segment}]` : index > 0 ? `.${String(segment)}` : String(segment)).join("");
1231
- }
1232
- /**
1233
- * Normalize a Standard Schema issue path to an array of `PropertyKey`.
1234
- *
1235
- * Standard Schema paths contain either bare `PropertyKey` values or
1236
- * `{ key: PropertyKey }` segment objects; both forms are flattened to
1237
- * plain `PropertyKey`. Returns an empty array for a root-level issue
1238
- * (`undefined`).
1239
- */
1240
- function isStandardPathSegment(segment) {
1241
- return typeof segment === "object" && segment !== null && "key" in segment;
1242
- }
1243
- function normalizeStandardPath(path) {
1244
- if (!path) return [];
1245
- return path.map((segment) => isStandardPathSegment(segment) ? segment.key : segment);
1246
- }
1247
- /**
1248
- * Normalize Standard Schema issues into canonical `ValidationIssue` objects.
1249
- *
1250
- * Applies an optional prefix (e.g. `["flags", "verbose"]`) to each issue
1251
- * path, then formats each path to its canonical dot-path string.
1252
- *
1253
- * @param issues — Raw Standard Schema issues from a failed validation
1254
- * @param prefix — Optional path segments prepended to each issue path
1255
- */
1256
- function normalizeStandardIssues(issues, prefix = []) {
1257
- return issues.map((issue) => {
1258
- const resolvedPath = normalizeStandardPath(issue.path);
1259
- const fullPath = [...prefix, ...resolvedPath];
1260
- return {
1261
- message: issue.message,
1262
- path: formatPath(fullPath)
1263
- };
1264
- });
1265
- }
1266
- //#endregion
1267
1056
  //#region src/parsing/schema.ts
1268
1057
  async function runSchema(schema, raw, path, issues) {
1269
1058
  const result = await schema["~standard"].validate(raw);
@@ -1976,4 +1765,4 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
1976
1765
  return await (hasInjectedIO(options?.io) ? withAmbientTerminalIO(io, invoke) : invoke());
1977
1766
  }
1978
1767
  //#endregion
1979
- export { normalizeFlag as C, normalizeArg as S, defineExtension as _, runInvocation as a, definingOf as b, sectionsFor as c, resolveArtifactDir as d, cloneCommandNode as f, registerFlag as g, createCommandNode as h, resolveTypedPath as i, visibleSectionsFor as l, validateCommandSections as m, executeInvocation as n, snapshotCommand as o, installExtensionContexts as p, prepareInvocation as r, isListed as s, SNAPSHOT_PATH_ENV as t, BUILD_OUT_DIR_ENV as u, contextSources as v, CrustError as w, validateContextAvailability as x, defineContext as y };
1768
+ export { CrustError as S, defineContext as _, runInvocation as a, normalizeArg as b, sectionsFor as c, installExtensionContexts as d, validateCommandSections as f, contextSources as g, defineExtension as h, resolveTypedPath as i, visibleSectionsFor as l, registerFlag as m, executeInvocation as n, snapshotCommand as o, createCommandNode as p, prepareInvocation as r, isListed as s, SNAPSHOT_PATH_ENV as t, cloneCommandNode as u, definingOf as v, normalizeFlag as x, validateContextAvailability as y };
package/dist/tooling.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { A as FlagSnapshot, G as ExtensionId, c as DeclaredDefault, k as CommandSnapshot, o as CommandSection, t as BUILD_OUT_DIR_ENV } from "./artifacts-D8Jlug-a.js";
1
+ import { Ct as FlagSnapshot, St as CommandSnapshot, g as DeclaredDefault, kt as ExtensionId, m as CommandSection } from "./context-Cud9frNp.js";
2
+ import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
2
3
  //#region src/command/invocation.d.ts
3
4
  /**
4
5
  * Snapshot subprocess protocol used by first-party build tooling.
package/dist/tooling.js CHANGED
@@ -1,4 +1,5 @@
1
- import { c as sectionsFor, l as visibleSectionsFor, s as isListed, t as SNAPSHOT_PATH_ENV, u as BUILD_OUT_DIR_ENV } from "./invocation-CIvHShiW.js";
1
+ import { c as sectionsFor, l as visibleSectionsFor, s as isListed, t as SNAPSHOT_PATH_ENV } from "./invocation-B17RvTLb.js";
2
+ import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
2
3
  //#region src/command/documentation.ts
3
4
  function isNonFiniteNumber(value) {
4
5
  return typeof value === "number" && !Number.isFinite(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/core",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Core library for the Crust CLI framework",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -48,9 +48,11 @@
48
48
  "prepack": "cp ../../LICENSE LICENSE",
49
49
  "postpack": "rm -f LICENSE"
50
50
  },
51
+ "dependencies": {
52
+ "@crustjs/utils": "^0.1.0"
53
+ },
51
54
  "devDependencies": {
52
55
  "@crustjs/config": "0.0.0",
53
- "@crustjs/utils": "0.0.0",
54
56
  "tsdown": "^0.23.0"
55
57
  },
56
58
  "peerDependencies": {