@lovrabet/cli-framework 1.0.2 → 1.0.4-beta.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/lib/framework/output.d.ts +14 -1
- package/lib/framework/output.js +25 -20
- package/lib/index.d.ts +9 -1
- package/lib/index.js +5 -1
- package/lib/service-tree/compile.d.ts +2 -0
- package/lib/service-tree/compile.js +128 -0
- package/lib/service-tree/index.d.ts +6 -0
- package/lib/service-tree/index.js +5 -0
- package/lib/service-tree/map-to.d.ts +2 -0
- package/lib/service-tree/map-to.js +27 -0
- package/lib/service-tree/match.d.ts +2 -0
- package/lib/service-tree/match.js +19 -0
- package/lib/service-tree/normalize.d.ts +2 -0
- package/lib/service-tree/normalize.js +35 -0
- package/lib/service-tree/path.d.ts +8 -0
- package/lib/service-tree/path.js +30 -0
- package/lib/service-tree/types.d.ts +132 -0
- package/lib/service-tree/types.js +1 -0
- package/lib/service-tree/validate.d.ts +2 -0
- package/lib/service-tree/validate.js +188 -0
- package/lib/utils/jq-sidecar.d.ts +11 -0
- package/lib/utils/jq-sidecar.js +28 -0
- package/package.json +5 -3
- package/sidecar/jq/README.md +24 -0
- package/sidecar/jq/darwin-arm64/jq +0 -0
- package/sidecar/jq/darwin-x64/jq +0 -0
- package/sidecar/jq/linux-arm64/jq +0 -0
- package/sidecar/jq/linux-x64/jq +0 -0
- package/sidecar/jq/win32-x64/jq.exe +0 -0
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* resolved from `JQ_PATH` or the system `PATH`.
|
|
11
11
|
*/
|
|
12
12
|
import type { CommandResult, OutputFormat, Risk } from "./types.js";
|
|
13
|
+
import { type JqBinaryResolverOptions } from "../utils/apply-jq-filter.js";
|
|
14
|
+
import { type CliErrorsShape } from "../errors.js";
|
|
13
15
|
/** Shared options passed through every internal print function. */
|
|
14
16
|
interface OutputOptions {
|
|
15
17
|
/** Full command label (e.g. `"api list"`). */
|
|
@@ -23,6 +25,15 @@ interface OutputOptions {
|
|
|
23
25
|
/** Optional jq expression for post-filtering (json/compress modes only). */
|
|
24
26
|
jqFilter?: string;
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for constructing a CLI-specific output formatter.
|
|
30
|
+
*/
|
|
31
|
+
export interface CreateFormatOutputOptions {
|
|
32
|
+
/** Error factory used when jq execution fails. */
|
|
33
|
+
cliErrors?: Pick<CliErrorsShape, "validation">;
|
|
34
|
+
/** jq binary resolution strategy for this concrete CLI. */
|
|
35
|
+
jqBinaryResolverOptions?: JqBinaryResolverOptions;
|
|
36
|
+
}
|
|
26
37
|
/**
|
|
27
38
|
* Central formatting entry point used by the runner adapter.
|
|
28
39
|
*
|
|
@@ -41,5 +52,7 @@ interface OutputOptions {
|
|
|
41
52
|
* formatOutput({ ok: true, data: { id: 1 } }, { command: "app list", risk: "read", format: "compress" });
|
|
42
53
|
* ```
|
|
43
54
|
*/
|
|
44
|
-
export declare function
|
|
55
|
+
export declare function createFormatOutput(options?: CreateFormatOutputOptions): <T>(result: CommandResult<T>, outputOptions: OutputOptions) => void;
|
|
56
|
+
/** Default framework formatter using `JQ_PATH` / `PATH` resolution only. */
|
|
57
|
+
export declare const formatOutput: <T>(result: CommandResult<T>, outputOptions: OutputOptions) => void;
|
|
45
58
|
export {};
|
package/lib/framework/output.js
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import { createJqFilter } from "../utils/apply-jq-filter.js";
|
|
14
14
|
import { createCliErrors } from "../errors.js";
|
|
15
|
-
const
|
|
15
|
+
const DEFAULT_CLI_ERRORS = createCliErrors({
|
|
16
16
|
cliBinName: "cli",
|
|
17
17
|
authRequiredHint: "",
|
|
18
18
|
configMissingHint: "",
|
|
19
19
|
notInProjectHint: "",
|
|
20
|
-
})
|
|
20
|
+
});
|
|
21
21
|
/**
|
|
22
22
|
* Central formatting entry point used by the runner adapter.
|
|
23
23
|
*
|
|
@@ -36,20 +36,25 @@ const applyJqFilter = createJqFilter(createCliErrors({
|
|
|
36
36
|
* formatOutput({ ok: true, data: { id: 1 } }, { command: "app list", risk: "read", format: "compress" });
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
export function createFormatOutput(options = {}) {
|
|
40
|
+
const applyJqFilter = createJqFilter(options.cliErrors ?? DEFAULT_CLI_ERRORS, options.jqBinaryResolverOptions);
|
|
41
|
+
return function formatOutput(result, outputOptions) {
|
|
42
|
+
switch (outputOptions.format) {
|
|
43
|
+
case "json":
|
|
44
|
+
printJson(result, outputOptions, applyJqFilter);
|
|
45
|
+
break;
|
|
46
|
+
case "compress":
|
|
47
|
+
printCompress(result, outputOptions, applyJqFilter);
|
|
48
|
+
break;
|
|
49
|
+
case "pretty":
|
|
50
|
+
default:
|
|
51
|
+
printPretty(result, outputOptions);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
52
55
|
}
|
|
56
|
+
/** Default framework formatter using `JQ_PATH` / `PATH` resolution only. */
|
|
57
|
+
export const formatOutput = createFormatOutput();
|
|
53
58
|
/**
|
|
54
59
|
* Wraps the command result in an {@link OutputEnvelope} by merging the
|
|
55
60
|
* command metadata from `options` and the result data / error.
|
|
@@ -81,8 +86,8 @@ function buildEnvelope(result, options) {
|
|
|
81
86
|
* @param result - The command result to serialize.
|
|
82
87
|
* @param options - Output options including jq filter.
|
|
83
88
|
*/
|
|
84
|
-
function printJson(result, options) {
|
|
85
|
-
writeJsonWithOptionalJq(`${JSON.stringify(buildEnvelope(result, options), null, 2)}\n`, options);
|
|
89
|
+
function printJson(result, options, applyJqFilter) {
|
|
90
|
+
writeJsonWithOptionalJq(`${JSON.stringify(buildEnvelope(result, options), null, 2)}\n`, options, applyJqFilter);
|
|
86
91
|
}
|
|
87
92
|
/**
|
|
88
93
|
* Formats and prints the result as single-line (compact) JSON.
|
|
@@ -91,8 +96,8 @@ function printJson(result, options) {
|
|
|
91
96
|
* @param result - The command result to serialize.
|
|
92
97
|
* @param options - Output options including jq filter.
|
|
93
98
|
*/
|
|
94
|
-
function printCompress(result, options) {
|
|
95
|
-
writeJsonWithOptionalJq(`${JSON.stringify(buildEnvelope(result, options))}\n`, options);
|
|
99
|
+
function printCompress(result, options, applyJqFilter) {
|
|
100
|
+
writeJsonWithOptionalJq(`${JSON.stringify(buildEnvelope(result, options))}\n`, options, applyJqFilter);
|
|
96
101
|
}
|
|
97
102
|
/**
|
|
98
103
|
* Writes a JSON string to stdout, optionally piping it through `jq`.
|
|
@@ -100,7 +105,7 @@ function printCompress(result, options) {
|
|
|
100
105
|
* @param jsonLine - Complete JSON line (must end with `\n`).
|
|
101
106
|
* @param options - Output options carrying the jq expression.
|
|
102
107
|
*/
|
|
103
|
-
function writeJsonWithOptionalJq(jsonLine, options) {
|
|
108
|
+
function writeJsonWithOptionalJq(jsonLine, options, applyJqFilter) {
|
|
104
109
|
const expr = options.jqFilter?.trim();
|
|
105
110
|
if (!expr) {
|
|
106
111
|
process.stdout.write(jsonLine);
|
package/lib/index.d.ts
CHANGED
|
@@ -205,5 +205,13 @@ Paging,
|
|
|
205
205
|
* @description List response wrapper with records and optional paging.
|
|
206
206
|
*/
|
|
207
207
|
ListResponse, } from "./framework/response.js";
|
|
208
|
-
export
|
|
208
|
+
export * from "./service-tree/index.js";
|
|
209
|
+
export { createFormatOutput, formatOutput } from "./framework/output.js";
|
|
210
|
+
export type {
|
|
211
|
+
/**
|
|
212
|
+
* @see ./framework/output.ts
|
|
213
|
+
* @description Options for constructing a CLI-specific output formatter.
|
|
214
|
+
*/
|
|
215
|
+
CreateFormatOutputOptions, } from "./framework/output.js";
|
|
216
|
+
export { resolveBundledJqPaths } from "./utils/jq-sidecar.js";
|
|
209
217
|
export { createJqFilter } from "./utils/apply-jq-filter.js";
|
package/lib/index.js
CHANGED
|
@@ -46,7 +46,11 @@ export {
|
|
|
46
46
|
extractList,
|
|
47
47
|
/** @see ./framework/response.ts */
|
|
48
48
|
extractPaging, } from "./framework/response.js";
|
|
49
|
+
// ─── Service Tree Manifest Utilities ─────────────────────────────────────────
|
|
50
|
+
export * from "./service-tree/index.js";
|
|
49
51
|
// ─── Output Formatting ─────────────────────────────────────────────────────────
|
|
50
|
-
export { formatOutput } from "./framework/output.js";
|
|
52
|
+
export { createFormatOutput, formatOutput } from "./framework/output.js";
|
|
53
|
+
// ─── Bundled jq Sidecar ────────────────────────────────────────────────────────
|
|
54
|
+
export { resolveBundledJqPaths } from "./utils/jq-sidecar.js";
|
|
51
55
|
// ─── JQ Filter ────────────────────────────────────────────────────────────────
|
|
52
56
|
export { createJqFilter } from "./utils/apply-jq-filter.js";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { stripParamsPrefix } from "./path.js";
|
|
2
|
+
export function compileServiceTreeCommand(command, input = {}) {
|
|
3
|
+
const params = cloneRecord(command.baseParams);
|
|
4
|
+
for (const rule of command.mapTo) {
|
|
5
|
+
const value = resolveRuleValue(rule, command, input);
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
continue;
|
|
8
|
+
if (rule.omitEmpty !== false && value === "")
|
|
9
|
+
continue;
|
|
10
|
+
const transformed = transformValue(value, rule.transform);
|
|
11
|
+
const targetPath = rule.operator
|
|
12
|
+
? `${stripParamsPrefix(rule.target)}.${rule.operator}`
|
|
13
|
+
: stripParamsPrefix(rule.target);
|
|
14
|
+
setDeep(params, targetPath, transformed);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
kind: command.target.kind,
|
|
18
|
+
command: command.target.command,
|
|
19
|
+
appRef: command.target.appRef,
|
|
20
|
+
datasetCode: command.target.datasetCode,
|
|
21
|
+
datatable: command.target.datatable,
|
|
22
|
+
sqlCode: command.target.sqlCode,
|
|
23
|
+
bffCode: command.target.bffCode,
|
|
24
|
+
bffId: command.target.bffId,
|
|
25
|
+
scriptName: command.target.scriptName,
|
|
26
|
+
params,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function resolveRuleValue(rule, command, input) {
|
|
30
|
+
if (rule.source === undefined || rule.source === "const" || rule.source.startsWith("const.")) {
|
|
31
|
+
return rule.value;
|
|
32
|
+
}
|
|
33
|
+
if (rule.source.startsWith("flags.")) {
|
|
34
|
+
const name = rule.source.slice("flags.".length);
|
|
35
|
+
return readFlagValue(name, command, input.flags || {});
|
|
36
|
+
}
|
|
37
|
+
if (rule.source.startsWith("args.")) {
|
|
38
|
+
const name = rule.source.slice("args.".length);
|
|
39
|
+
return readArgValue(name, command, input.args || {});
|
|
40
|
+
}
|
|
41
|
+
if (rule.source.startsWith("context.")) {
|
|
42
|
+
const name = rule.source.slice("context.".length);
|
|
43
|
+
return readPath(input.context || {}, name);
|
|
44
|
+
}
|
|
45
|
+
if (rule.source.startsWith("ctx.")) {
|
|
46
|
+
const name = rule.source.slice("ctx.".length);
|
|
47
|
+
return readPath(input.context || {}, name);
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
function readFlagValue(name, command, flags) {
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(flags, name))
|
|
53
|
+
return flags[name];
|
|
54
|
+
const flag = command.flags.find((item) => item.name === name || item.cliName === name);
|
|
55
|
+
if (flag) {
|
|
56
|
+
if (Object.prototype.hasOwnProperty.call(flags, flag.cliName))
|
|
57
|
+
return flags[flag.cliName];
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(flags, flag.name))
|
|
59
|
+
return flags[flag.name];
|
|
60
|
+
if (flag.default !== undefined)
|
|
61
|
+
return flag.default;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function readArgValue(name, command, args) {
|
|
66
|
+
if (Array.isArray(args)) {
|
|
67
|
+
const index = /^\d+$/.test(name)
|
|
68
|
+
? Number(name)
|
|
69
|
+
: command.args.findIndex((arg) => arg.name === name);
|
|
70
|
+
return index >= 0 ? args[index] : undefined;
|
|
71
|
+
}
|
|
72
|
+
return args[name];
|
|
73
|
+
}
|
|
74
|
+
function readPath(value, path) {
|
|
75
|
+
if (!path)
|
|
76
|
+
return value;
|
|
77
|
+
const parts = path.split(".").filter(Boolean);
|
|
78
|
+
let cursor = value;
|
|
79
|
+
for (const part of parts) {
|
|
80
|
+
if (!isRecord(cursor) || !Object.prototype.hasOwnProperty.call(cursor, part)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
cursor = cursor[part];
|
|
84
|
+
}
|
|
85
|
+
return cursor;
|
|
86
|
+
}
|
|
87
|
+
function transformValue(value, transform) {
|
|
88
|
+
if (!transform)
|
|
89
|
+
return value;
|
|
90
|
+
if (transform === "string")
|
|
91
|
+
return String(value);
|
|
92
|
+
if (transform === "number")
|
|
93
|
+
return typeof value === "number" ? value : Number(value);
|
|
94
|
+
if (transform === "boolean") {
|
|
95
|
+
if (typeof value === "boolean")
|
|
96
|
+
return value;
|
|
97
|
+
if (typeof value === "string")
|
|
98
|
+
return value === "true" || value === "1";
|
|
99
|
+
return Boolean(value);
|
|
100
|
+
}
|
|
101
|
+
if (transform === "json") {
|
|
102
|
+
if (typeof value !== "string")
|
|
103
|
+
return value;
|
|
104
|
+
return JSON.parse(value);
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
function setDeep(target, path, value) {
|
|
109
|
+
const parts = path.split(".").filter(Boolean);
|
|
110
|
+
let cursor = target;
|
|
111
|
+
parts.forEach((part, index) => {
|
|
112
|
+
if (index === parts.length - 1) {
|
|
113
|
+
cursor[part] = value;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const existing = cursor[part];
|
|
117
|
+
if (!isRecord(existing)) {
|
|
118
|
+
cursor[part] = {};
|
|
119
|
+
}
|
|
120
|
+
cursor = cursor[part];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function cloneRecord(value) {
|
|
124
|
+
return JSON.parse(JSON.stringify(value));
|
|
125
|
+
}
|
|
126
|
+
function isRecord(value) {
|
|
127
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
128
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { NormalizedServiceTree, NormalizedServiceTreeCommand, NormalizedServiceTreeFlag, NormalizedServiceTreeTarget, ServiceTreeAppBinding, ServiceTreeArg, ServiceTreeCommand, ServiceTreeCompileInput, ServiceTreeDatasetResolver, ServiceTreeDatasetResolverInput, ServiceTreeExecutionPlan, ServiceTreeFlag, ServiceTreeManifest, ServiceTreeMapTo, ServiceTreeMapToObject, ServiceTreeMapToRule, ServiceTreeMapTransform, ServiceTreeMatch, ServiceTreeServiceInfo, ServiceTreeTarget, ServiceTreeTargetCommand, ServiceTreeTargetKind, ServiceTreeValidationIssue, ServiceTreeValidationReport, ServiceTreeValueType, } from "./types.js";
|
|
2
|
+
export { compileServiceTreeCommand } from "./compile.js";
|
|
3
|
+
export { matchServiceTreeCommand } from "./match.js";
|
|
4
|
+
export { normalizeServiceTreeManifest } from "./normalize.js";
|
|
5
|
+
export { isCommandSegment, isDatatableName, isDatasetCode, isServiceCode, pathToString, splitServiceTreePath, stripParamsPrefix, toKebabCase, } from "./path.js";
|
|
6
|
+
export { validateServiceTreeManifest } from "./validate.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { compileServiceTreeCommand } from "./compile.js";
|
|
2
|
+
export { matchServiceTreeCommand } from "./match.js";
|
|
3
|
+
export { normalizeServiceTreeManifest } from "./normalize.js";
|
|
4
|
+
export { isCommandSegment, isDatatableName, isDatasetCode, isServiceCode, pathToString, splitServiceTreePath, stripParamsPrefix, toKebabCase, } from "./path.js";
|
|
5
|
+
export { validateServiceTreeManifest } from "./validate.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function normalizeMapTo(mapTo, flags = []) {
|
|
2
|
+
const rules = [];
|
|
3
|
+
if (Array.isArray(mapTo)) {
|
|
4
|
+
rules.push(...mapTo.map((rule) => ({ ...rule })));
|
|
5
|
+
}
|
|
6
|
+
else if (mapTo && typeof mapTo === "object") {
|
|
7
|
+
for (const [source, target] of Object.entries(mapTo)) {
|
|
8
|
+
if (typeof target === "string") {
|
|
9
|
+
rules.push({ source, target });
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
rules.push({ source, ...target });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const flag of flags) {
|
|
17
|
+
if (!flag.mapTo)
|
|
18
|
+
continue;
|
|
19
|
+
if (typeof flag.mapTo === "string") {
|
|
20
|
+
rules.push({ source: `flags.${flag.name}`, target: flag.mapTo });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
rules.push({ source: `flags.${flag.name}`, ...flag.mapTo });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return rules;
|
|
27
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { splitServiceTreePath } from "./path.js";
|
|
2
|
+
export function matchServiceTreeCommand(tree, input) {
|
|
3
|
+
const path = splitServiceTreePath(input);
|
|
4
|
+
let best;
|
|
5
|
+
for (const command of tree.commands) {
|
|
6
|
+
if (!startsWith(path, command.fullPath))
|
|
7
|
+
continue;
|
|
8
|
+
const remainingArgs = path.slice(command.fullPath.length);
|
|
9
|
+
if (!best || command.fullPath.length > best.command.fullPath.length) {
|
|
10
|
+
best = { command, remainingArgs };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return best;
|
|
14
|
+
}
|
|
15
|
+
function startsWith(path, prefix) {
|
|
16
|
+
if (prefix.length > path.length)
|
|
17
|
+
return false;
|
|
18
|
+
return prefix.every((segment, index) => path[index] === segment);
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { normalizeMapTo } from "./map-to.js";
|
|
2
|
+
import { splitServiceTreePath, toKebabCase } from "./path.js";
|
|
3
|
+
const DEFAULT_PROTOCOL = "lovrabet.service-tree/v1";
|
|
4
|
+
export function normalizeServiceTreeManifest(manifest) {
|
|
5
|
+
const serviceCode = manifest.service.code;
|
|
6
|
+
return {
|
|
7
|
+
protocol: manifest.protocol || DEFAULT_PROTOCOL,
|
|
8
|
+
version: manifest.version,
|
|
9
|
+
service: { ...manifest.service },
|
|
10
|
+
appBindings: { ...(manifest.appBindings || {}) },
|
|
11
|
+
commands: manifest.commands.map((command) => {
|
|
12
|
+
const path = splitServiceTreePath(command.path);
|
|
13
|
+
const fullPath = path[0] === serviceCode ? path : [serviceCode, ...path];
|
|
14
|
+
const flags = (command.flags || []).map((flag) => ({
|
|
15
|
+
...flag,
|
|
16
|
+
cliName: flag.cliName ? toKebabCase(flag.cliName) : toKebabCase(flag.name),
|
|
17
|
+
}));
|
|
18
|
+
return {
|
|
19
|
+
path,
|
|
20
|
+
fullPath,
|
|
21
|
+
cliPath: fullPath.join(" "),
|
|
22
|
+
description: command.description,
|
|
23
|
+
risk: command.risk || "read",
|
|
24
|
+
args: command.args || [],
|
|
25
|
+
flags,
|
|
26
|
+
target: { ...command.target },
|
|
27
|
+
baseParams: {
|
|
28
|
+
...(command.target.params || {}),
|
|
29
|
+
...(command.params || {}),
|
|
30
|
+
},
|
|
31
|
+
mapTo: normalizeMapTo(command.mapTo, flags),
|
|
32
|
+
};
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function splitServiceTreePath(path: string | string[]): string[];
|
|
2
|
+
export declare function toKebabCase(value: string): string;
|
|
3
|
+
export declare function isCommandSegment(value: string): boolean;
|
|
4
|
+
export declare function isServiceCode(value: string): boolean;
|
|
5
|
+
export declare function isDatatableName(value: string): boolean;
|
|
6
|
+
export declare function isDatasetCode(value: string): boolean;
|
|
7
|
+
export declare function stripParamsPrefix(path: string): string;
|
|
8
|
+
export declare function pathToString(path: string[]): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function splitServiceTreePath(path) {
|
|
2
|
+
const parts = Array.isArray(path) ? path : path.trim().split(/\s+/);
|
|
3
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
4
|
+
}
|
|
5
|
+
export function toKebabCase(value) {
|
|
6
|
+
return value
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
9
|
+
.replace(/[_\s]+/g, "-")
|
|
10
|
+
.replace(/-+/g, "-")
|
|
11
|
+
.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
export function isCommandSegment(value) {
|
|
14
|
+
return /^[a-z][a-z0-9-]*$/.test(value);
|
|
15
|
+
}
|
|
16
|
+
export function isServiceCode(value) {
|
|
17
|
+
return /^[a-z][a-z0-9-]*$/.test(value);
|
|
18
|
+
}
|
|
19
|
+
export function isDatatableName(value) {
|
|
20
|
+
return /^[A-Za-z][A-Za-z0-9_]*$/.test(value);
|
|
21
|
+
}
|
|
22
|
+
export function isDatasetCode(value) {
|
|
23
|
+
return /^[a-f0-9]{32}$/i.test(value);
|
|
24
|
+
}
|
|
25
|
+
export function stripParamsPrefix(path) {
|
|
26
|
+
return path.startsWith("params.") ? path.slice("params.".length) : path;
|
|
27
|
+
}
|
|
28
|
+
export function pathToString(path) {
|
|
29
|
+
return path.join(" ");
|
|
30
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Risk } from "../framework/types.js";
|
|
2
|
+
export type ServiceTreeTargetKind = "data" | "sql" | "bff";
|
|
3
|
+
export type ServiceTreeTargetCommand = "filter" | "getOne" | "aggregate" | "create" | "batchCreate" | "update" | "delete" | "exec";
|
|
4
|
+
export type ServiceTreeValueType = "string" | "number" | "boolean" | "json";
|
|
5
|
+
export type ServiceTreeMapTransform = ServiceTreeValueType;
|
|
6
|
+
export interface ServiceTreeServiceInfo {
|
|
7
|
+
code: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ServiceTreeAppBinding {
|
|
12
|
+
app?: string;
|
|
13
|
+
appcode?: string;
|
|
14
|
+
appCode?: string;
|
|
15
|
+
env?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ServiceTreeArg {
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ServiceTreeMapToRule {
|
|
23
|
+
source?: string;
|
|
24
|
+
target: string;
|
|
25
|
+
value?: unknown;
|
|
26
|
+
transform?: ServiceTreeMapTransform;
|
|
27
|
+
operator?: string;
|
|
28
|
+
omitEmpty?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export type ServiceTreeMapToObject = Record<string, string | Omit<ServiceTreeMapToRule, "source">>;
|
|
31
|
+
export type ServiceTreeMapTo = ServiceTreeMapToRule[] | ServiceTreeMapToObject;
|
|
32
|
+
export interface ServiceTreeFlag {
|
|
33
|
+
name: string;
|
|
34
|
+
cliName?: string;
|
|
35
|
+
type: ServiceTreeValueType;
|
|
36
|
+
description?: string;
|
|
37
|
+
required?: boolean;
|
|
38
|
+
default?: unknown;
|
|
39
|
+
enum?: string[];
|
|
40
|
+
mapTo?: string | Omit<ServiceTreeMapToRule, "source">;
|
|
41
|
+
}
|
|
42
|
+
export interface ServiceTreeTarget {
|
|
43
|
+
kind: ServiceTreeTargetKind;
|
|
44
|
+
command: ServiceTreeTargetCommand;
|
|
45
|
+
appRef?: string;
|
|
46
|
+
datasetCode?: string;
|
|
47
|
+
datatable?: string;
|
|
48
|
+
sqlCode?: string;
|
|
49
|
+
bffCode?: string;
|
|
50
|
+
bffId?: string | number;
|
|
51
|
+
scriptName?: string;
|
|
52
|
+
params?: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
export interface ServiceTreeCommand {
|
|
55
|
+
path: string | string[];
|
|
56
|
+
description?: string;
|
|
57
|
+
risk?: Risk;
|
|
58
|
+
args?: ServiceTreeArg[];
|
|
59
|
+
flags?: ServiceTreeFlag[];
|
|
60
|
+
target: ServiceTreeTarget;
|
|
61
|
+
params?: Record<string, unknown>;
|
|
62
|
+
mapTo?: ServiceTreeMapTo;
|
|
63
|
+
}
|
|
64
|
+
export interface ServiceTreeManifest {
|
|
65
|
+
protocol?: string;
|
|
66
|
+
version: string;
|
|
67
|
+
service: ServiceTreeServiceInfo;
|
|
68
|
+
appBindings?: Record<string, ServiceTreeAppBinding>;
|
|
69
|
+
commands: ServiceTreeCommand[];
|
|
70
|
+
}
|
|
71
|
+
export interface NormalizedServiceTreeFlag extends ServiceTreeFlag {
|
|
72
|
+
cliName: string;
|
|
73
|
+
}
|
|
74
|
+
export interface NormalizedServiceTreeTarget extends ServiceTreeTarget {
|
|
75
|
+
command: ServiceTreeTargetCommand;
|
|
76
|
+
}
|
|
77
|
+
export interface NormalizedServiceTreeCommand {
|
|
78
|
+
path: string[];
|
|
79
|
+
fullPath: string[];
|
|
80
|
+
cliPath: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
risk: Risk;
|
|
83
|
+
args: ServiceTreeArg[];
|
|
84
|
+
flags: NormalizedServiceTreeFlag[];
|
|
85
|
+
target: NormalizedServiceTreeTarget;
|
|
86
|
+
baseParams: Record<string, unknown>;
|
|
87
|
+
mapTo: ServiceTreeMapToRule[];
|
|
88
|
+
}
|
|
89
|
+
export interface NormalizedServiceTree {
|
|
90
|
+
protocol: string;
|
|
91
|
+
version: string;
|
|
92
|
+
service: ServiceTreeServiceInfo;
|
|
93
|
+
appBindings: Record<string, ServiceTreeAppBinding>;
|
|
94
|
+
commands: NormalizedServiceTreeCommand[];
|
|
95
|
+
}
|
|
96
|
+
export interface ServiceTreeValidationIssue {
|
|
97
|
+
level: "error" | "warning";
|
|
98
|
+
path: string;
|
|
99
|
+
message: string;
|
|
100
|
+
}
|
|
101
|
+
export interface ServiceTreeValidationReport {
|
|
102
|
+
ok: boolean;
|
|
103
|
+
errors: ServiceTreeValidationIssue[];
|
|
104
|
+
warnings: ServiceTreeValidationIssue[];
|
|
105
|
+
}
|
|
106
|
+
export interface ServiceTreeMatch {
|
|
107
|
+
command: NormalizedServiceTreeCommand;
|
|
108
|
+
remainingArgs: string[];
|
|
109
|
+
}
|
|
110
|
+
export interface ServiceTreeCompileInput {
|
|
111
|
+
flags?: Record<string, unknown>;
|
|
112
|
+
args?: Record<string, unknown> | string[];
|
|
113
|
+
context?: Record<string, unknown>;
|
|
114
|
+
}
|
|
115
|
+
export interface ServiceTreeExecutionPlan {
|
|
116
|
+
kind: ServiceTreeTargetKind;
|
|
117
|
+
command: ServiceTreeTargetCommand;
|
|
118
|
+
appRef?: string;
|
|
119
|
+
datasetCode?: string;
|
|
120
|
+
datatable?: string;
|
|
121
|
+
sqlCode?: string;
|
|
122
|
+
bffCode?: string;
|
|
123
|
+
bffId?: string | number;
|
|
124
|
+
scriptName?: string;
|
|
125
|
+
params: Record<string, unknown>;
|
|
126
|
+
}
|
|
127
|
+
export interface ServiceTreeDatasetResolverInput {
|
|
128
|
+
appRef?: string;
|
|
129
|
+
datatable: string;
|
|
130
|
+
commandPath: string[];
|
|
131
|
+
}
|
|
132
|
+
export type ServiceTreeDatasetResolver = (input: ServiceTreeDatasetResolverInput) => string | undefined | Promise<string | undefined>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { isCommandSegment, isDatatableName, isDatasetCode, isServiceCode, splitServiceTreePath, toKebabCase, } from "./path.js";
|
|
2
|
+
const DATA_COMMANDS = new Set(["filter", "getOne", "aggregate", "create", "batchCreate", "update", "delete"]);
|
|
3
|
+
const SQL_COMMANDS = new Set(["exec"]);
|
|
4
|
+
const BFF_COMMANDS = new Set(["exec"]);
|
|
5
|
+
const VALUE_TYPES = new Set(["string", "number", "boolean", "json"]);
|
|
6
|
+
export function validateServiceTreeManifest(manifest) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
if (!isRecord(manifest)) {
|
|
9
|
+
push(issues, "error", "$", "Manifest must be a JSON object.");
|
|
10
|
+
return buildReport(issues);
|
|
11
|
+
}
|
|
12
|
+
if (typeof manifest.version !== "string" || !manifest.version.trim()) {
|
|
13
|
+
push(issues, "error", "$.version", "version is required.");
|
|
14
|
+
}
|
|
15
|
+
if (!isRecord(manifest.service)) {
|
|
16
|
+
push(issues, "error", "$.service", "service is required.");
|
|
17
|
+
}
|
|
18
|
+
else if (typeof manifest.service.code !== "string" || !isServiceCode(manifest.service.code)) {
|
|
19
|
+
push(issues, "error", "$.service.code", "service.code must be lower kebab-case.");
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(manifest.commands) || manifest.commands.length === 0) {
|
|
22
|
+
push(issues, "error", "$.commands", "commands must be a non-empty array.");
|
|
23
|
+
return buildReport(issues);
|
|
24
|
+
}
|
|
25
|
+
const serviceCode = isRecord(manifest.service) && typeof manifest.service.code === "string"
|
|
26
|
+
? manifest.service.code
|
|
27
|
+
: "";
|
|
28
|
+
const seenPaths = new Set();
|
|
29
|
+
manifest.commands.forEach((command, index) => {
|
|
30
|
+
validateCommand(command, index, serviceCode, seenPaths, issues);
|
|
31
|
+
});
|
|
32
|
+
return buildReport(issues);
|
|
33
|
+
}
|
|
34
|
+
function validateCommand(command, index, serviceCode, seenPaths, issues) {
|
|
35
|
+
const base = `$.commands[${index}]`;
|
|
36
|
+
if (!isRecord(command)) {
|
|
37
|
+
push(issues, "error", base, "Command must be an object.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const path = splitServiceTreePath(command.path || []);
|
|
41
|
+
if (path.length === 0) {
|
|
42
|
+
push(issues, "error", `${base}.path`, "path is required.");
|
|
43
|
+
}
|
|
44
|
+
for (const [partIndex, segment] of path.entries()) {
|
|
45
|
+
if (!isCommandSegment(segment)) {
|
|
46
|
+
push(issues, "error", `${base}.path[${partIndex}]`, "Command path segments must be lower kebab-case.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const fullPath = path[0] === serviceCode ? path : [serviceCode, ...path];
|
|
50
|
+
const fullKey = fullPath.join(" ");
|
|
51
|
+
if (seenPaths.has(fullKey)) {
|
|
52
|
+
push(issues, "error", `${base}.path`, `Duplicate command path: ${fullKey}.`);
|
|
53
|
+
}
|
|
54
|
+
seenPaths.add(fullKey);
|
|
55
|
+
validateTarget(command.target, `${base}.target`, issues);
|
|
56
|
+
validateFlags(command.flags, `${base}.flags`, issues);
|
|
57
|
+
validateMapTo(command.mapTo, `${base}.mapTo`, issues);
|
|
58
|
+
}
|
|
59
|
+
function validateTarget(target, path, issues) {
|
|
60
|
+
if (!isRecord(target)) {
|
|
61
|
+
push(issues, "error", path, "target is required.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const typed = target;
|
|
65
|
+
if (typed.kind === "data") {
|
|
66
|
+
if (!DATA_COMMANDS.has(typed.command)) {
|
|
67
|
+
push(issues, "error", `${path}.command`, "data target command is not supported.");
|
|
68
|
+
}
|
|
69
|
+
if (!typed.datasetCode && !typed.datatable) {
|
|
70
|
+
push(issues, "error", path, "data target requires datasetCode or datatable.");
|
|
71
|
+
}
|
|
72
|
+
if (typed.datasetCode && !isDatasetCode(typed.datasetCode)) {
|
|
73
|
+
push(issues, "error", `${path}.datasetCode`, "datasetCode must be a 32-character code.");
|
|
74
|
+
}
|
|
75
|
+
if (typed.datatable && !isDatatableName(typed.datatable)) {
|
|
76
|
+
push(issues, "error", `${path}.datatable`, "datatable must be a valid physical table name.");
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (typed.kind === "sql") {
|
|
81
|
+
if (!SQL_COMMANDS.has(typed.command)) {
|
|
82
|
+
push(issues, "error", `${path}.command`, "sql target command is not supported.");
|
|
83
|
+
}
|
|
84
|
+
if (!typed.sqlCode) {
|
|
85
|
+
push(issues, "error", `${path}.sqlCode`, "sql target requires sqlCode.");
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (typed.kind === "bff") {
|
|
90
|
+
if (!BFF_COMMANDS.has(typed.command)) {
|
|
91
|
+
push(issues, "error", `${path}.command`, "bff target command is not supported.");
|
|
92
|
+
}
|
|
93
|
+
if (!typed.bffCode && !typed.bffId && !typed.scriptName) {
|
|
94
|
+
push(issues, "error", path, "bff target requires bffCode, bffId, or scriptName.");
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
push(issues, "error", `${path}.kind`, "target.kind must be data, sql, or bff.");
|
|
99
|
+
}
|
|
100
|
+
function validateFlags(flags, path, issues) {
|
|
101
|
+
if (flags === undefined)
|
|
102
|
+
return;
|
|
103
|
+
if (!Array.isArray(flags)) {
|
|
104
|
+
push(issues, "error", path, "flags must be an array.");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
flags.forEach((flag, index) => {
|
|
109
|
+
const flagPath = `${path}[${index}]`;
|
|
110
|
+
if (!isRecord(flag)) {
|
|
111
|
+
push(issues, "error", flagPath, "flag must be an object.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (typeof flag.name !== "string" || !flag.name.trim()) {
|
|
115
|
+
push(issues, "error", `${flagPath}.name`, "flag.name is required.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (seen.has(flag.name)) {
|
|
119
|
+
push(issues, "error", `${flagPath}.name`, `Duplicate flag name: ${flag.name}.`);
|
|
120
|
+
}
|
|
121
|
+
seen.add(flag.name);
|
|
122
|
+
const cliName = typeof flag.cliName === "string" ? flag.cliName : toKebabCase(flag.name);
|
|
123
|
+
if (cliName !== toKebabCase(cliName)) {
|
|
124
|
+
push(issues, "error", `${flagPath}.cliName`, "flag.cliName must be kebab-case.");
|
|
125
|
+
}
|
|
126
|
+
if (typeof flag.type !== "string" || !VALUE_TYPES.has(flag.type)) {
|
|
127
|
+
push(issues, "error", `${flagPath}.type`, "flag.type must be string, number, boolean, or json.");
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function validateMapTo(mapTo, path, issues) {
|
|
132
|
+
if (mapTo === undefined)
|
|
133
|
+
return;
|
|
134
|
+
if (Array.isArray(mapTo)) {
|
|
135
|
+
mapTo.forEach((rule, index) => validateMapRule(rule, `${path}[${index}]`, issues));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (isRecord(mapTo)) {
|
|
139
|
+
for (const [source, target] of Object.entries(mapTo)) {
|
|
140
|
+
if (!isValidSource(source)) {
|
|
141
|
+
push(issues, "error", `${path}.${source}`, "mapTo source must start with flags., args., const., context., or ctx.");
|
|
142
|
+
}
|
|
143
|
+
if (typeof target === "string") {
|
|
144
|
+
if (!target.trim())
|
|
145
|
+
push(issues, "error", `${path}.${source}`, "mapTo target is required.");
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
validateMapRule({ source, ...target }, `${path}.${source}`, issues);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
push(issues, "error", path, "mapTo must be an object or an array.");
|
|
154
|
+
}
|
|
155
|
+
function validateMapRule(rule, path, issues) {
|
|
156
|
+
if (!isRecord(rule)) {
|
|
157
|
+
push(issues, "error", path, "mapTo rule must be an object.");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (rule.source !== undefined && (typeof rule.source !== "string" || !isValidSource(rule.source))) {
|
|
161
|
+
push(issues, "error", `${path}.source`, "mapTo source must start with flags., args., const., context., or ctx.");
|
|
162
|
+
}
|
|
163
|
+
if (typeof rule.target !== "string" || !rule.target.trim()) {
|
|
164
|
+
push(issues, "error", `${path}.target`, "mapTo target is required.");
|
|
165
|
+
}
|
|
166
|
+
if (rule.transform !== undefined && (typeof rule.transform !== "string" || !VALUE_TYPES.has(rule.transform))) {
|
|
167
|
+
push(issues, "error", `${path}.transform`, "mapTo transform must be string, number, boolean, or json.");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function isValidSource(source) {
|
|
171
|
+
return source === "const"
|
|
172
|
+
|| source.startsWith("flags.")
|
|
173
|
+
|| source.startsWith("args.")
|
|
174
|
+
|| source.startsWith("const.")
|
|
175
|
+
|| source.startsWith("context.")
|
|
176
|
+
|| source.startsWith("ctx.");
|
|
177
|
+
}
|
|
178
|
+
function buildReport(issues) {
|
|
179
|
+
const errors = issues.filter((issue) => issue.level === "error");
|
|
180
|
+
const warnings = issues.filter((issue) => issue.level === "warning");
|
|
181
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
182
|
+
}
|
|
183
|
+
function push(issues, level, path, message) {
|
|
184
|
+
issues.push({ level, path, message });
|
|
185
|
+
}
|
|
186
|
+
function isRecord(value) {
|
|
187
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
188
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves bundled jq candidate paths for the current runtime platform.
|
|
3
|
+
*
|
|
4
|
+
* The result is ordered from most preferred to least preferred. Callers should
|
|
5
|
+
* test the paths in order and use the first one that exists.
|
|
6
|
+
*
|
|
7
|
+
* @param platform - Node.js platform, defaults to `process.platform`.
|
|
8
|
+
* @param arch - Node.js architecture, defaults to `process.arch`.
|
|
9
|
+
* @returns Candidate absolute paths to bundled jq executables.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveBundledJqPaths(platform?: NodeJS.Platform, arch?: NodeJS.Architecture): string[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
/** Supported bundled jq targets keyed by `platform:arch`. */
|
|
4
|
+
const JQ_TARGETS = {
|
|
5
|
+
"darwin:arm64": ["darwin-arm64", "darwin-x64"],
|
|
6
|
+
"darwin:x64": ["darwin-x64"],
|
|
7
|
+
"linux:arm64": ["linux-arm64"],
|
|
8
|
+
"linux:x64": ["linux-x64"],
|
|
9
|
+
"win32:arm64": ["win32-x64"],
|
|
10
|
+
"win32:x64": ["win32-x64"],
|
|
11
|
+
};
|
|
12
|
+
const CURRENT_FILE = fileURLToPath(import.meta.url);
|
|
13
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(CURRENT_FILE), "../..");
|
|
14
|
+
const SIDECAR_ROOT = path.join(PACKAGE_ROOT, "sidecar", "jq");
|
|
15
|
+
/**
|
|
16
|
+
* Resolves bundled jq candidate paths for the current runtime platform.
|
|
17
|
+
*
|
|
18
|
+
* The result is ordered from most preferred to least preferred. Callers should
|
|
19
|
+
* test the paths in order and use the first one that exists.
|
|
20
|
+
*
|
|
21
|
+
* @param platform - Node.js platform, defaults to `process.platform`.
|
|
22
|
+
* @param arch - Node.js architecture, defaults to `process.arch`.
|
|
23
|
+
* @returns Candidate absolute paths to bundled jq executables.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveBundledJqPaths(platform = process.platform, arch = process.arch) {
|
|
26
|
+
const targetDirs = JQ_TARGETS[`${platform}:${arch}`] ?? [];
|
|
27
|
+
return targetDirs.map((dir) => path.join(SIDECAR_ROOT, dir, platform === "win32" ? "jq.exe" : "jq"));
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrabet/cli-framework",
|
|
3
|
-
"version": "1.0.2",
|
|
3
|
+
"version": "1.0.4-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"lib"
|
|
14
|
+
"lib",
|
|
15
|
+
"sidecar"
|
|
15
16
|
],
|
|
16
17
|
"publishConfig": {
|
|
17
18
|
"access": "public"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"build": "rm -rf lib && tsc && echo 'cli-framework build done'",
|
|
22
|
+
"update:jq-sidecar": "node scripts/update-jq-sidecar.mjs",
|
|
21
23
|
"beta-release": "sh scripts/update-beta-version.sh && bun run build && git push && git push origin --tag && bun publish --tag beta",
|
|
22
24
|
"release": "sh scripts/update-latest-version.sh && bun run build && git push && git push origin --tag && bun publish",
|
|
23
25
|
"typecheck": "tsc --noEmit",
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
30
32
|
"@types/node": "^24.5.2",
|
|
31
|
-
"typescript": "
|
|
33
|
+
"typescript": "5.9.3",
|
|
32
34
|
"vitest": "^4.1.2"
|
|
33
35
|
}
|
|
34
36
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This directory vendors jq binaries that ship with `@lovrabet/cli-framework`.
|
|
2
|
+
|
|
3
|
+
Consumer CLIs such as `@lovrabet/rabetbase-cli` and `@lovrabet/lovrabet-cli`
|
|
4
|
+
resolve these binaries through the framework package.
|
|
5
|
+
|
|
6
|
+
- Upstream project: `jqlang/jq`
|
|
7
|
+
- Version: `jq-1.8.1`
|
|
8
|
+
- License: `MIT`
|
|
9
|
+
- Release source: `https://github.com/jqlang/jq/releases/tag/jq-1.8.1`
|
|
10
|
+
|
|
11
|
+
Bundled targets:
|
|
12
|
+
|
|
13
|
+
- `darwin-arm64/jq`
|
|
14
|
+
- `darwin-x64/jq`
|
|
15
|
+
- `linux-arm64/jq`
|
|
16
|
+
- `linux-x64/jq`
|
|
17
|
+
- `win32-x64/jq.exe`
|
|
18
|
+
|
|
19
|
+
Update policy:
|
|
20
|
+
|
|
21
|
+
1. Download binaries only from the official jq release assets.
|
|
22
|
+
2. Keep file names stable (`jq` / `jq.exe`) so runtime resolution stays simple.
|
|
23
|
+
3. Refresh the sidecar with `npm run update:jq-sidecar -- jq-<version>` when the bundled jq version changes.
|
|
24
|
+
4. Update this README when the bundled jq version changes.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|