@crustjs/core 0.0.18 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,2 +1,232 @@
1
- // @bun
2
- import{a as O,b as K,c as T,d as I,f as B}from"./shared/chunk-q8y07jw2.js";function P(j){return{meta:{name:j},localFlags:{},effectiveFlags:{},args:void 0,subCommands:{},plugins:[],preRun:void 0,run:void 0,postRun:void 0}}function X(j,J){let q={};for(let[Q,Z]of Object.entries(j))if(Z.inherit===!0)q[Q]=Z;for(let[Q,Z]of Object.entries(J))q[Q]=Z;return q}function E(j,J){for(let[q,Q]of Object.entries(j)){let Z=Q.meta.aliases;if(!Z)continue;if(Z.includes(J))return{canonicalName:q,node:Q}}return null}function D(j,J){let q=[j.meta.name],Q=j,Z=J;while(Z.length>0){let $=Q.subCommands;if(!$||Object.keys($).length===0)break;let G=Z[0];if(!G||G.startsWith("-"))break;if(G in $&&$[G]){Q=$[G],q.push(G),Z=Z.slice(1);continue}let H=E($,G);if(H){Q=H.node,q.push(H.canonicalName),Z=Z.slice(1);continue}if(Q.run)break;throw new K("COMMAND_NOT_FOUND",`Unknown command "${G}".`,{input:G,available:Object.keys($),commandPath:[...q],parentCommand:Q})}return{command:Q,argv:Z,commandPath:q}}function f(j){for(let[J,q]of Object.entries(j)){if(J.startsWith("no-")){let Q=J.slice(3);throw new K("DEFINITION",`Flag "--${J}" must not use "no-" prefix; define "${Q}" and negate with "--no-${Q}"`)}if(q.short?.startsWith("no-"))throw new K("DEFINITION",`Short alias "-${q.short}" on "--${J}" must not use "no-" prefix (reserved for negation)`);if(q.aliases){for(let Q of q.aliases)if(Q.startsWith("no-"))throw new K("DEFINITION",`Alias "--${Q}" on "--${J}" must not use "no-" prefix (reserved for negation)`)}}}var N="CRUST_INTERNAL_VALIDATE_ONLY",b="CRUST_INTERNAL_VALIDATE_FORCE_EXIT",x=130,C="__CRUST_VALIDATE_RESULT__";function k(){let j=new Map;return{get(J){return j.get(J)},has(J){return j.has(J)},set(J,q){j.set(J,q)},delete(J){return j.delete(J)}}}function L(j){if(!(j instanceof Error))return!1;return j.name==="CancelledError"}function h(j,J){j.effectiveFlags=X(J,j.localFlags);for(let q of Object.values(j.subCommands))h(q,j.effectiveFlags)}function y(j){return{addFlag(J,q,Q){if(q in J.effectiveFlags)j?.push(`Plugin flag "--${q}" on "${J.meta.name}" overrides existing flag`);J.effectiveFlags[q]=Q},addSubCommand(J,q,Q){if(!q.trim())throw new K("DEFINITION","addSubCommand: name must be a non-empty string");if(J.subCommands[q]){j?.push(`Plugin subcommand "${q}" on "${J.meta.name}" skipped (already exists)`);return}try{B({canonicalName:q,aliases:Q.meta.aliases},J.subCommands,q)}catch(Z){let $=Z instanceof Error?Z.message:String(Z);j?.push(`Plugin subcommand "${q}" on "${J.meta.name}" skipped: ${$}`);return}h(Q,J.effectiveFlags),J.subCommands[q]=Q}}}async function F(j,J,q){for(let Q of j){if(!Q.setup)continue;await Q.setup(J,q)}}async function A(j,J,q){let Q=j.map((G)=>G.middleware).filter((G)=>Boolean(G)),Z=-1,$=async(G)=>{if(G<=Z)throw new K("DEFINITION","Plugin middleware called next() multiple times");if(Z=G,G===Q.length){await q();return}let H=Q[G];if(!H)throw new K("DEFINITION","Plugin middleware stack is invalid");await H(J,()=>$(G+1))};await $(0)}function S(j){let J=[...j.plugins];for(let q of Object.values(j.subCommands))J.push(...S(q));return J}function w(j){let J={};for(let[q,Q]of Object.entries(j))J[q]={...Q,aliases:Q.aliases?[...Q.aliases]:void 0};return J}function v(j){let J={};for(let[q,Q]of Object.entries(j.subCommands))J[q]=v(Q);return{meta:{...j.meta},localFlags:w(j.localFlags),effectiveFlags:w(j.effectiveFlags),args:j.args?j.args.map((q)=>({...q})):void 0,subCommands:J,plugins:[...j.plugins],preRun:j.preRun,run:j.run,postRun:j.postRun}}function _(j){if(Object.freeze(j),Object.freeze(j.localFlags),Object.freeze(j.effectiveFlags),Object.freeze(j.meta),Object.freeze(j.plugins),j.args)Object.freeze(j.args);for(let J of Object.values(j.subCommands))_(J);Object.freeze(j.subCommands)}class M{_node;_inheritedFlags;constructor(j){if(!j.trim())throw new K("DEFINITION","meta.name must be a non-empty string");this._node=P(j),this._inheritedFlags={}}static _createChild(j,J){let q=new M(j);return q._inheritedFlags=J,q}_clone(j){let J=Object.create(Object.getPrototypeOf(this)),q={...this._node,localFlags:{...this._node.localFlags},effectiveFlags:{...this._node.effectiveFlags},subCommands:{...this._node.subCommands},plugins:[...this._node.plugins],meta:{...this._node.meta},args:this._node.args?[...this._node.args]:void 0,...j};return J._node=q,J._inheritedFlags=this._inheritedFlags,J}meta(j){return this._clone({meta:{...this._node.meta,...j}})}flags(j){f(j);let J={};for(let[q,Q]of Object.entries(j))J[q]={...Q};return this._clone({localFlags:J,effectiveFlags:X(this._inheritedFlags,J)})}args(j){let J=j.map((q)=>({...q}));return this._clone({args:J})}run(j){return this._clone({run:j})}preRun(j){return this._clone({preRun:j})}postRun(j){return this._clone({postRun:j})}use(j){return this._clone({plugins:[...this._node.plugins,j]})}sub(j){if(!j.trim())throw new K("DEFINITION","Subcommand name must be a non-empty string");let J=X(this._inheritedFlags,this._node.localFlags);return M._createChild(j,J)}command(j,J){if(typeof j==="string"){let $=j;if(!J)throw new K("DEFINITION","command(name, cb) requires a callback");if(!$.trim())throw new K("DEFINITION","Subcommand name must be a non-empty string");if(this._node.subCommands[$])throw new K("DEFINITION",`Subcommand "${$}" is already registered`);let G=X(this._inheritedFlags,this._node.localFlags),H=M._createChild($,G),z=J(H);B({canonicalName:$,aliases:z._node.meta.aliases},this._node.subCommands,$);let W={...z._node,effectiveFlags:X(z._inheritedFlags,z._node.localFlags)};return this._clone({subCommands:{...this._node.subCommands,[$]:W}})}let q=j,Q=q._node.meta.name;if(!Q.trim())throw new K("DEFINITION","Subcommand name must be a non-empty string");if(this._node.subCommands[Q])throw new K("DEFINITION",`Subcommand "${Q}" is already registered`);B({canonicalName:Q,aliases:q._node.meta.aliases},this._node.subCommands,Q);let Z={...q._node,effectiveFlags:X(q._inheritedFlags,q._node.localFlags)};return this._clone({subCommands:{...this._node.subCommands,[Q]:Z}})}async prepareCommandTree(j){let J=j?.argv??[],q=v(this._node),Q=S(q),Z=[],$=k(),G={argv:[...J],rootCommand:q,state:$},H=y(Z);try{await F(Q,G,H)}catch(W){if(L(W))throw W;if(W instanceof K)throw W;if(W instanceof Error)throw W;throw new K("DEFINITION",String(W))}_(q);let{validateCommandTree:z}=await import("./shared/chunk-1670njz2.js");return z(q),{root:q,warnings:Z}}async execute(j){let J=j?.argv??process.argv.slice(2),q=this._node,Q=S(q),Z=[],$=k(),G={argv:[...J],rootCommand:q,state:$},H=y(Z);try{await F(Q,G,H)}catch(W){if(L(W)){process.exitCode=x;return}if(W instanceof K){console.error(`Error: ${W.message}`),process.exitCode=1;return}let U=W instanceof Error?W.message:String(W);console.error(`Error: ${U}`),process.exitCode=1;return}if(_(q),process.env[N]==="1"){let W=(async()=>{try{let{validateCommandTree:U}=await import("./shared/chunk-1670njz2.js");U(q);for(let Y of Z)console.warn(`Warning: ${Y}`);return{ok:!0}}catch(U){let Y=U instanceof Error?U.message:String(U);return console.error(Y),process.exitCode=1,{ok:!1,error:U}}})();if(globalThis[C]=W,await W,process.env[b]==="1")return process.exit(process.exitCode??0);return}for(let W of Z)console.warn(`Warning: ${W}`);let z={argv:[...J],rootCommand:q,state:$,route:null,input:null};try{let W,U;try{let Y=D(q,[...J]);z.route=Y,W=Y.command,U=T(W,Y.argv),z.input=U}catch(Y){await A(Q,z,async()=>{throw Y});return}await A(Q,z,async()=>{if(I(W,U),!W.run)return;let Y={args:U.args,flags:U.flags,rawArgs:U.rawArgs,command:W},R;try{if(W.preRun)await W.preRun(Y);await W.run(Y)}catch(V){R=V}if(W.postRun)try{await W.postRun(Y)}catch(V){if(!R)R=V;else console.error(`Error in postRun: ${V instanceof Error?V.message:String(V)}`)}if(R)throw R})}catch(W){if(L(W)){process.exitCode=x;return}if(W instanceof K){console.error(`Error: ${W.message}`),process.exitCode=1;return}if(W instanceof Error){let U=new K("EXECUTION",W.message).withCause(W);console.error(`Error: ${U.message}`),process.exitCode=1;return}console.error(`Error: ${String(W)}`),process.exitCode=1}}}export{I as validateParsed,D as resolveCommand,T as parseArgs,N as VALIDATION_MODE_ENV,b as VALIDATION_FORCE_EXIT_ENV,K as CrustError,M as Crust};
1
+ import { _ as definingOf, a as runInvocation, b as normalizeFlag, d as installExtensionContexts, f as validateCommandSections, g as defineContext, h as defineExtension, i as prepareInvocation, m as registerFlag, o as snapshotCommand, p as createCommandNode, r as executeInvocation, u as cloneCommandNode, v as validateContextAvailability, x as CrustError, y as normalizeArg } from "./invocation-DcA5FqF7.js";
2
+ //#region src/identity.ts
3
+ /** Mint an Extension identity from any non-blank, trimmed string. */
4
+ function defineExtensionId(id) {
5
+ if (!id.trim() || id !== id.trim()) throw new CrustError("DEFINITION", "Extension id must be a non-empty, trimmed string", {
6
+ subject: "extension",
7
+ reason: "empty-id"
8
+ });
9
+ return id;
10
+ }
11
+ //#endregion
12
+ //#region src/api/flags.ts
13
+ function defineFlag(name, def) {
14
+ return normalizeFlag(name, {
15
+ ...def,
16
+ name
17
+ });
18
+ }
19
+ function defineArg(name, def) {
20
+ return normalizeArg({
21
+ ...def,
22
+ name
23
+ });
24
+ }
25
+ //#endregion
26
+ //#region src/command/crust.ts
27
+ const commandDefinitionInternal = Symbol.for("crust.commandDefinition");
28
+ function materializeCommandDefinition(definition, parent, extensionName) {
29
+ const internal = definition[commandDefinitionInternal];
30
+ const name = definition.name;
31
+ const owner = extensionName ? `Extension "${extensionName}" command "${name}"` : `Command "${name}"`;
32
+ const definitionDetails = (reason) => ({
33
+ subject: extensionName ? "extension" : "command",
34
+ name: extensionName ?? name,
35
+ reason
36
+ });
37
+ const child = new Crust(name);
38
+ for (const [flagName, def] of Object.entries(parent.ownedFlags)) registerFlag(child._node, flagName, def, "owned");
39
+ child._node.contexts = parent.contexts.map((context) => ({ ...context }));
40
+ const configured = internal.recipe(child);
41
+ if (!(configured instanceof Crust) || configured._ancestorOwnedFlags !== child._ancestorOwnedFlags) throw new CrustError("DEFINITION", `${owner} definition must return the same command builder it received`, definitionDetails("foreign-command-builder"));
42
+ if (configured._node.extensions.length > 0) throw new CrustError("DEFINITION", `${owner} cannot register Extensions inside command definitions`, definitionDetails("nested-command-extension"));
43
+ const childNode = cloneCommandNode(configured._node);
44
+ validateContextAvailability(childNode.contexts.map(({ instance }) => instance), childNode.demands);
45
+ childNode.meta = {
46
+ name,
47
+ ...internal.meta
48
+ };
49
+ return childNode;
50
+ }
51
+ function resolveCommandName(name, aliases = []) {
52
+ if (name.trim() === "") throw new CrustError("DEFINITION", "Command name must be a non-empty string", {
53
+ subject: "command",
54
+ name,
55
+ reason: "empty-name"
56
+ });
57
+ if (name === "__proto__") throw new CrustError("DEFINITION", "Command name \"__proto__\" is reserved", {
58
+ subject: "command",
59
+ name,
60
+ reason: "reserved-name"
61
+ });
62
+ if (aliases.includes(name)) throw new CrustError("DEFINITION", `Command "${name}" must not list its own canonical name as an alias`, {
63
+ subject: "command",
64
+ name,
65
+ reason: "alias-collision"
66
+ });
67
+ return name;
68
+ }
69
+ function isCommandRecipe(value) {
70
+ return typeof value === "function";
71
+ }
72
+ function defineCommand(nameInput, configOrRecipe, maybeRecipe) {
73
+ const hasConfig = !isCommandRecipe(configOrRecipe);
74
+ const config = hasConfig ? configOrRecipe : {};
75
+ const recipe = hasConfig ? maybeRecipe : configOrRecipe;
76
+ const name = resolveCommandName(nameInput, config.aliases);
77
+ for (const alias of config.aliases ?? []) if (alias === "" || /[ \t\n\r\v\f]/.test(alias) || alias.startsWith("-")) throw new CrustError("DEFINITION", `Command "${name}" has an invalid alias "${alias}"`);
78
+ const { sections, version: _rootVersion, ...metaRest } = config;
79
+ const meta = {
80
+ ...metaRest,
81
+ ...config.aliases ? { aliases: [...config.aliases] } : {},
82
+ ...sections ? { sections: validateCommandSections(name, sections) } : {}
83
+ };
84
+ const internal = {
85
+ name,
86
+ recipe,
87
+ meta
88
+ };
89
+ const named = (defName) => {
90
+ return Object.freeze({
91
+ name: defName,
92
+ as: (newName) => named(resolveCommandName(newName, meta.aliases)),
93
+ [commandDefinitionInternal]: Object.freeze({
94
+ ...internal,
95
+ name: defName
96
+ })
97
+ });
98
+ };
99
+ return named(name);
100
+ }
101
+ function dedupeExtensions(extensions) {
102
+ return extensions.filter((e, i) => extensions.findLastIndex((x) => x.id === e.id) === i);
103
+ }
104
+ var Crust = class {
105
+ /** @internal */
106
+ _node;
107
+ /** @internal — Recipe-builder lineage anchor, unique per materialization and preserved by clones */
108
+ _ancestorOwnedFlags;
109
+ constructor(nameInput, ...metadata) {
110
+ const meta = metadata[0] ?? {};
111
+ const name = resolveCommandName(nameInput);
112
+ this._node = createCommandNode(name);
113
+ if (meta.description !== void 0) this._node.meta.description = meta.description;
114
+ if (meta.version !== void 0) this._node.meta.version = meta.version;
115
+ if (meta.usage !== void 0) this._node.meta.usage = meta.usage;
116
+ if (meta.sections !== void 0) this._node.meta.sections = validateCommandSections(name, meta.sections);
117
+ this._ancestorOwnedFlags = {};
118
+ }
119
+ /** @internal — Clone this builder with a new node, preserving generics. */
120
+ _clone(nodeOverrides) {
121
+ const cloned = Object.create(Object.getPrototypeOf(this));
122
+ cloned._node = {
123
+ ...cloneCommandNode({
124
+ ...this._node,
125
+ subCommands: {}
126
+ }),
127
+ subCommands: { ...this._node.subCommands },
128
+ ...nodeOverrides
129
+ };
130
+ cloned._ancestorOwnedFlags = this._ancestorOwnedFlags;
131
+ return cloned;
132
+ }
133
+ flags(...defs) {
134
+ const cloned = this._clone({});
135
+ for (const def of defs) {
136
+ const { name, ...rest } = def;
137
+ registerFlag(cloned._node, name, rest, "local");
138
+ }
139
+ return cloned;
140
+ }
141
+ args(...defs) {
142
+ const combined = [...this._node.args, ...defs.map(normalizeArg)];
143
+ const seen = /* @__PURE__ */ new Set();
144
+ for (const [index, definition] of combined.entries()) {
145
+ if (seen.has(definition.name)) throw new CrustError("DEFINITION", `Argument name "${definition.name}" is already defined`, {
146
+ subject: "argument",
147
+ name: definition.name,
148
+ reason: "duplicate-arg"
149
+ });
150
+ seen.add(definition.name);
151
+ if (definition.variadic === true && index !== combined.length - 1) throw new CrustError("DEFINITION", `Only the last positional argument can be variadic; "${definition.name}" is not last`, {
152
+ subject: "argument",
153
+ name: definition.name,
154
+ reason: "variadic-position"
155
+ });
156
+ }
157
+ return this._clone({ args: combined });
158
+ }
159
+ use(...factories) {
160
+ return this._clone({ demands: [...this._node.demands, ...factories.map(definingOf)] });
161
+ }
162
+ provide(...inputs) {
163
+ const instances = inputs.map(definingOf);
164
+ validateContextAvailability([...this._node.contexts.map(({ instance }) => instance), ...instances], instances);
165
+ const cloned = this._clone({ contexts: [...this._node.contexts, ...instances.map((instance) => ({ instance }))] });
166
+ for (const instance of instances) for (const [name, definition] of Object.entries(instance.ownedFlags)) registerFlag(cloned._node, name, definition, "owned");
167
+ return cloned;
168
+ }
169
+ /**
170
+ * Define the Command Action — the function that implements this
171
+ * command's behavior after its inputs are ready.
172
+ *
173
+ * The action receives a {@link CrustCommandContext} with `args` typed from
174
+ * `.args()` and `flags` typed from the accumulated `Flags`.
175
+ *
176
+ * Calling `.action()` again replaces the command behavior on the new builder.
177
+ * The original builder is not mutated.
178
+ *
179
+ * @param action - The Command Action function
180
+ * @returns A new `Crust` instance with the action registered
181
+ */
182
+ action(action) {
183
+ return this._clone({ run: action });
184
+ }
185
+ extend(...inputs) {
186
+ const extensions = inputs.map(definingOf);
187
+ const activeExtensions = dedupeExtensions([...this._node.extensions, ...extensions]);
188
+ const node = installExtensionContexts(this._node, activeExtensions, new Set(extensions.map((extension) => extension.id)));
189
+ validateContextAvailability(node.contexts.map(({ instance }) => instance), activeExtensions.flatMap((extension) => [...extension.uses, ...extension.provides ?? []]));
190
+ return this._clone({
191
+ ...node,
192
+ extensions: activeExtensions
193
+ });
194
+ }
195
+ add(...definitions) {
196
+ return this._addDefinitions(definitions);
197
+ }
198
+ command(name, recipe) {
199
+ const definition = defineCommand(name, recipe);
200
+ return this._addDefinitions([definition]);
201
+ }
202
+ _addDefinitions(definitions) {
203
+ const subCommands = { ...this._node.subCommands };
204
+ for (const definition of definitions) {
205
+ const spellings = [definition.name, ...definition[commandDefinitionInternal].meta.aliases ?? []];
206
+ for (const sibling of Object.values(subCommands)) if ([sibling.meta.name, ...sibling.meta.aliases ?? []].some((name) => spellings.includes(name))) throw new CrustError("DEFINITION", `Command name "${definition.name}" is already registered on this command`, {
207
+ subject: "command",
208
+ name: definition.name,
209
+ reason: "command-collision"
210
+ });
211
+ const childNode = materializeCommandDefinition(definition, this._node);
212
+ subCommands[definition.name] = childNode;
213
+ }
214
+ return this._clone({ subCommands });
215
+ }
216
+ async snapshot() {
217
+ return snapshotCommand(prepareInvocation(this._node, materializeCommandDefinition).rootNode);
218
+ }
219
+ async run(path, ...args) {
220
+ const structuredInput = args[0] ?? {};
221
+ const io = args[1];
222
+ return await runInvocation(this._node, {
223
+ path,
224
+ input: structuredInput
225
+ }, io, materializeCommandDefinition);
226
+ }
227
+ async execute(options) {
228
+ return await executeInvocation(this._node, options, materializeCommandDefinition);
229
+ }
230
+ };
231
+ //#endregion
232
+ export { Crust, CrustError, defineArg, defineCommand, defineContext, defineExtension, defineExtensionId, defineFlag };