ingestron 0.12.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/CHANGELOG.md +12 -0
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +71 -0
- package/SECURITY.md +32 -0
- package/THIRD_PARTY_NOTICES.md +3656 -0
- package/build/cli/cli/index.d.ts +2 -0
- package/build/cli/cli/index.js +588 -0
- package/build/cli/cli/operation-worker.d.ts +1 -0
- package/build/cli/cli/operation-worker.js +5 -0
- package/build/cli/cli/output.d.ts +3 -0
- package/build/cli/cli/output.js +130 -0
- package/build/cli/cli/plugin-browser.d.ts +14 -0
- package/build/cli/cli/plugin-browser.js +74 -0
- package/build/cli/cli/presentation.d.ts +12 -0
- package/build/cli/cli/presentation.js +57 -0
- package/build/cli/cli/workflow-commands.d.ts +15 -0
- package/build/cli/cli/workflow-commands.js +405 -0
- package/build/cli/mcp/index.d.ts +3 -0
- package/build/cli/mcp/index.js +66 -0
- package/build/cli/version.d.ts +1 -0
- package/build/cli/version.js +13 -0
- package/package.json +73 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Presentation is deliberately separate from operation results. JSON is the
|
|
2
|
+
* lossless API; terminal output is bounded, plain text and actionable. Never
|
|
3
|
+
* serialise arbitrary plugin data or resolved bindings in the default summary. */
|
|
4
|
+
import { friendlyReference } from "@ingestron/core/adapter";
|
|
5
|
+
import { stringify } from "yaml";
|
|
6
|
+
export function exitCode(result) {
|
|
7
|
+
if (result.ok)
|
|
8
|
+
return 0;
|
|
9
|
+
const codes = result.diagnostics.map((d) => d.code);
|
|
10
|
+
if (codes.includes("CANCELLED"))
|
|
11
|
+
return 130;
|
|
12
|
+
if (codes.includes("NOT_IMPLEMENTED"))
|
|
13
|
+
return 4;
|
|
14
|
+
if (codes.some((c) => ["STALE", "OWNER", "CONFLICT"].includes(c)))
|
|
15
|
+
return 5;
|
|
16
|
+
if (codes.some((c) => [
|
|
17
|
+
"INPUT",
|
|
18
|
+
"BINDING",
|
|
19
|
+
"CAPABILITY",
|
|
20
|
+
"PACKAGE",
|
|
21
|
+
"OPTION",
|
|
22
|
+
"COMMAND",
|
|
23
|
+
"SCHEMA",
|
|
24
|
+
].includes(c)))
|
|
25
|
+
return 3;
|
|
26
|
+
return 2;
|
|
27
|
+
}
|
|
28
|
+
const clean = (value) => String(value).replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
|
|
29
|
+
function lines(title, items, empty) {
|
|
30
|
+
if (!items.length)
|
|
31
|
+
return empty;
|
|
32
|
+
return `${title} (${items.length})\n${items
|
|
33
|
+
.slice(0, 20)
|
|
34
|
+
.map((i) => ` ${clean(i)}`)
|
|
35
|
+
.join("\n")}${items.length > 20 ? "\n More results available with --json or --verbose." : ""}`;
|
|
36
|
+
}
|
|
37
|
+
export function terminal(result, verbose = false, args = {}) {
|
|
38
|
+
if (!result.ok)
|
|
39
|
+
return result.diagnostics
|
|
40
|
+
.map((d) => `Error: ${clean(d.message)}${verbose ? ` [${d.code}]` : ""}${d.file ? `\n File: ${clean(d.file)}${clean(d.pointer ?? "")}` : ""}${d.hint ? `\n Next: ${clean(d.hint)}` : ""}`)
|
|
41
|
+
.join("\n");
|
|
42
|
+
const value = result.result;
|
|
43
|
+
if (verbose)
|
|
44
|
+
return `${result.operation.replaceAll("_", " ")}\n${typeof value === "string" ? value : stringify(value).trimEnd()}`;
|
|
45
|
+
if (value?.apiVersion === "ingestron.change/v1")
|
|
46
|
+
return (lines("Proposed file changes", value.changes.map((c) => `${c.before === null ? "Create" : "Update"} ${c.path}`), "No file changes needed.") + "\nPreview only; no files changed.");
|
|
47
|
+
if (value?.applied)
|
|
48
|
+
return lines("Saved files", value.files, "No file changes needed.");
|
|
49
|
+
switch (result.operation) {
|
|
50
|
+
case "plugin_browse":
|
|
51
|
+
return ((value.length
|
|
52
|
+
? "Installed plugins\n\n" +
|
|
53
|
+
value
|
|
54
|
+
.map((p) => ` ${clean(p.id).padEnd(13)} ${clean(p.name)} [${clean(p.kindLabel)} · ${clean(p.availability)}]\n ${clean(p.description)}\n ${clean(p.url)}`)
|
|
55
|
+
.join("\n\n")
|
|
56
|
+
: "No installed plugins match this search.") +
|
|
57
|
+
"\n\nBrowse versions: ingestron plugin versions <owner/repository>\nGuided install: ingestron plugin install\nInstall: ingestron plugin install owner/repository@1.2.3");
|
|
58
|
+
case "plugin_versions":
|
|
59
|
+
return (lines(`Versions for ${value.provider}`, value.versions.map((v) => `${v.reference}${v.ambiguous ? " (conflicting tags; select a commit)" : ""}`), "No stable version tags found.") +
|
|
60
|
+
"\n\n" +
|
|
61
|
+
value.evidence +
|
|
62
|
+
"\nInstall: ingestron plugin install <owner/repository>@<version>");
|
|
63
|
+
case "packages_install":
|
|
64
|
+
return `${value.cached ? "Using cached" : "Installed"} plugin ${clean(friendlyReference(args.reference ?? value.reference ?? "package"))}.${value.informationFile ? `\nPlugin information and licence evidence: ${clean(value.informationFile)}` : ""}`;
|
|
65
|
+
case "packages_list":
|
|
66
|
+
return lines("Installed plugins", Object.keys(value.packages).map(friendlyReference), "No plugins installed.\nNext: ingestron plugin install <owner/repository>@<version>");
|
|
67
|
+
case "source_list":
|
|
68
|
+
return lines("Sources", value.map((s) => `${s.id} ${s.type}${s.format ? "/" + s.format : ""} ${s.execution}`), "No sources configured.\nNext: ingestron source add <name> --type <type>");
|
|
69
|
+
case "source_show":
|
|
70
|
+
return `Source ${clean(value.id)}\n Type: ${clean(value.type)}${value.format ? ` (${clean(value.format)})` : ""}\n Execution: ${clean(value.selected.execution)}\n Environment: ${clean(value.selected.environment)}\n Provider: ${clean(value.selected.provider ?? "project default")}\nUse --verbose to inspect connection references.`;
|
|
71
|
+
case "contract_list":
|
|
72
|
+
return lines("Contracts", value.map((c) => `${c.id} ${c.status} ${c.path}`), "No contracts drafted.\nNext: ingestron contract draft <name> --metadata <file>");
|
|
73
|
+
case "contract_show":
|
|
74
|
+
return (`Contract ${clean(value.id)} (${clean(value.status)})\n` +
|
|
75
|
+
lines("Entities", (value.schema ?? []).map((s) => `${s.physicalName ?? s.name}: ${(s.properties ?? []).length} fields`), "No entities.") +
|
|
76
|
+
"\nUse --verbose to review the complete contract.");
|
|
77
|
+
case "contract_check":
|
|
78
|
+
return `Contract valid: ${clean(value.path)}${value.status === "draft" ? "\nStatus: draft; review business requirements before use." : ""}\n${clean(value.evidence)}`;
|
|
79
|
+
case "contract_validate":
|
|
80
|
+
return `Contract valid for native table authoring (${value.columns.length} columns).`;
|
|
81
|
+
case "inspect":
|
|
82
|
+
return lines("Flows", value.flows.map((f) => `${f.id} ${f.kind} ${f.tables.length} tables, ${f.outputs.length} outputs`), "No flows configured.\nNext: ingestron flow add <name> --contract <name> --standard <standard>");
|
|
83
|
+
case "flow_show":
|
|
84
|
+
return `Flow ${clean(value.id)}\n Kind: ${clean(value.kind)}\n Tables: ${Object.keys(value.tables ?? {})
|
|
85
|
+
.map(clean)
|
|
86
|
+
.join(", ") || "none"}\n Outputs: ${Object.keys(value.publishes ?? {})
|
|
87
|
+
.map(clean)
|
|
88
|
+
.join(", ") || "none"}\nUse --verbose to inspect configuration.`;
|
|
89
|
+
case "validate":
|
|
90
|
+
return value.mode === "draft"
|
|
91
|
+
? `Draft configuration checked (${value.flows} flows).\n${value.pending.length} unresolved inputs. Strict build readiness has not been checked.`
|
|
92
|
+
: `Project checks passed${value.nodes === undefined ? "" : ` (${value.nodes} planned nodes)`}.\nValidation is local; no platform connection was tested.`;
|
|
93
|
+
case "doctor":
|
|
94
|
+
return `Project ${clean(value.project)} (${clean(value.environment)})\n${value.pending.length} unresolved inputs.\n${clean(value.platformAccess)}\nNext: ${clean(value.next)}`;
|
|
95
|
+
case "runtime_prepare":
|
|
96
|
+
case "run":
|
|
97
|
+
case "run_status":
|
|
98
|
+
return `Run ${clean(value.id)}: ${clean(value.status)} (${clean(value.action)}).\nTarget: ${clean(value.configuration)}; flows: ${value.flows.map(clean).join(", ")}.\nUse ingestron run status ${clean(value.id)} to inspect the receipt.`;
|
|
99
|
+
case "build":
|
|
100
|
+
return `Built ${value.packages?.length ?? 1} project package(s) into ${clean(value.directory)}.\n${clean(value.evidence)}`;
|
|
101
|
+
case "generate":
|
|
102
|
+
return "Generated native assets.\nUse check --output <directory> to verify local output.";
|
|
103
|
+
case "validate_output":
|
|
104
|
+
return "Generated output checks passed.\nOwnership and syntax checked locally; no platform connection was tested.";
|
|
105
|
+
case "environments":
|
|
106
|
+
return lines("Environments", value.available.map((e) => `${e}${e === value.selected ? " (selected)" : ""}`), "No environments configured.");
|
|
107
|
+
case "tables":
|
|
108
|
+
return lines("Flow tables", value.map((f) => `${f.flow}: ${f.tables.join(", ") || "none"}`), "No tables configured.");
|
|
109
|
+
case "catalogue":
|
|
110
|
+
return lines(args.kind ?? "Capabilities", value.map((v) => `${v.id}${v.version ? "@" + v.version : ""}${v.description ? " " + v.description : ""}`), `No ${args.kind ?? "capabilities"} available.\nNext: ingestron plugin list`);
|
|
111
|
+
case "provider_commands":
|
|
112
|
+
return lines("Plugin operations", value.commands.map((c) => `${c.name} ${c.description}`), "No plugin operations available.");
|
|
113
|
+
case "plugin_command":
|
|
114
|
+
case "provider_command":
|
|
115
|
+
return "Plugin operation completed locally.\nUse --json, --verbose or --out to inspect its result.";
|
|
116
|
+
case "plan":
|
|
117
|
+
return `Local build plan ready (${value.nodes.length} nodes).\nUse --json or --verbose to inspect the plan.`;
|
|
118
|
+
case "diff":
|
|
119
|
+
return `Local differences: ${value.added.length} added, ${value.changed.length} changed, ${value.removed.length} removed.`;
|
|
120
|
+
case "config_explain":
|
|
121
|
+
return `Configuration: ${clean(value.path)} (${clean(value.environment)})\nUse --verbose to inspect declared and resolved values.`;
|
|
122
|
+
case "resolve":
|
|
123
|
+
return `Resolved project ${clean(value.project.id)} (${value.flows.length} flows).\n${value.pending.length} unresolved inputs. Use --verbose to inspect configuration.`;
|
|
124
|
+
case "read":
|
|
125
|
+
return value.content;
|
|
126
|
+
case "schema":
|
|
127
|
+
return "Schema available. Use --json for tools or --verbose to inspect it.";
|
|
128
|
+
}
|
|
129
|
+
return `${result.operation.replaceAll("_", " ")} completed.\nUse --json for structured data or --verbose for details.`;
|
|
130
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Browser {
|
|
2
|
+
select(message: string, options: {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
hint?: string;
|
|
6
|
+
}[]): Promise<string>;
|
|
7
|
+
search(): Promise<string>;
|
|
8
|
+
details(text: string): void;
|
|
9
|
+
}
|
|
10
|
+
/** Read-only navigation of installed plugin metadata. */
|
|
11
|
+
export declare function browsePlugins(ui: Browser, query?: string, initialKind?: string, filters?: {
|
|
12
|
+
engine?: string;
|
|
13
|
+
}, root?: string): Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { browseProviders, pluginKindLabel } from "@ingestron/core/adapter";
|
|
2
|
+
/** Read-only navigation of installed plugin metadata. */
|
|
3
|
+
export async function browsePlugins(ui, query = "", initialKind, filters = {}, root = process.cwd()) {
|
|
4
|
+
browseProviders(root, query, initialKind, filters); // Validate filters even when catalogue is empty.
|
|
5
|
+
let kind = initialKind;
|
|
6
|
+
let listing = !!query || !!kind;
|
|
7
|
+
let page = 0;
|
|
8
|
+
for (;;) {
|
|
9
|
+
if (!listing) {
|
|
10
|
+
const choice = await ui.select("Browse plugins", [
|
|
11
|
+
{ value: "all", label: "Browse all plugins" },
|
|
12
|
+
{ value: "search", label: "Search plugins" },
|
|
13
|
+
...["provider", "model-pack", "activity-pack", "report-pack"].map((value) => ({ value, label: pluginKindLabel(value) })),
|
|
14
|
+
{
|
|
15
|
+
value: "connector",
|
|
16
|
+
label: "Connectors",
|
|
17
|
+
hint: "Installed connector packages",
|
|
18
|
+
},
|
|
19
|
+
{ value: "exit", label: "Finish browsing" },
|
|
20
|
+
]);
|
|
21
|
+
if (choice === "exit")
|
|
22
|
+
return;
|
|
23
|
+
kind = ["all", "search"].includes(choice) ? undefined : choice;
|
|
24
|
+
query = choice === "search" ? await ui.search() : "";
|
|
25
|
+
listing = true;
|
|
26
|
+
page = 0;
|
|
27
|
+
}
|
|
28
|
+
const plugins = browseProviders(root, query, kind, filters);
|
|
29
|
+
const visible = plugins.slice(page * 25, (page + 1) * 25);
|
|
30
|
+
const selected = await ui.select(plugins.length
|
|
31
|
+
? `Select a plugin (${plugins.length} results, page ${page + 1})`
|
|
32
|
+
: "No plugins match; change category or search", [
|
|
33
|
+
...visible.map((p) => ({
|
|
34
|
+
value: p.reference,
|
|
35
|
+
label: `${p.name} [${p.kindLabel}]`,
|
|
36
|
+
hint: `${p.availability} · ${p.description}`,
|
|
37
|
+
})),
|
|
38
|
+
...(page > 0 ? [{ value: ":previous", label: "Previous page" }] : []),
|
|
39
|
+
...((page + 1) * 25 < plugins.length
|
|
40
|
+
? [{ value: ":next", label: "Next page" }]
|
|
41
|
+
: []),
|
|
42
|
+
{ value: ":search", label: "Search this category" },
|
|
43
|
+
{ value: ":back", label: "Change category / browse all" },
|
|
44
|
+
{ value: ":exit", label: "Finish browsing" },
|
|
45
|
+
]);
|
|
46
|
+
if (selected === ":next") {
|
|
47
|
+
page++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (selected === ":previous") {
|
|
51
|
+
page--;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (selected === ":exit")
|
|
55
|
+
return;
|
|
56
|
+
if (selected === ":back") {
|
|
57
|
+
listing = false;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (selected === ":search") {
|
|
61
|
+
query = await ui.search();
|
|
62
|
+
page = 0;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const plugin = plugins.find((p) => p.reference === selected);
|
|
66
|
+
ui.details(`${plugin.name} (${plugin.id})\n${plugin.kindLabel} · ${plugin.availability}\n\n${plugin.description}\n${plugin.evidence}\n\nLicence: ${plugin.licence}\nDocumentation: ${plugin.documentation}\nSource and package documentation: ${plugin.url}\n\nExact-version licences/notices and provenance are saved to .ingestron/plugin-info/ during installation. Engine compatibility is package-specific; installed metadata is not runtime verification.`);
|
|
67
|
+
const action = await ui.select("Next step", [
|
|
68
|
+
{ value: "back", label: "Back to results" },
|
|
69
|
+
{ value: "exit", label: "Finish browsing" },
|
|
70
|
+
]);
|
|
71
|
+
if (action === "exit")
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Context, OperationName, Result } from "@ingestron/core";
|
|
2
|
+
export interface Presentation {
|
|
3
|
+
tty: boolean;
|
|
4
|
+
plain?: boolean;
|
|
5
|
+
colour?: boolean;
|
|
6
|
+
progress?: boolean;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function richTerminal(options: Presentation, env?: NodeJS.ProcessEnv): boolean;
|
|
10
|
+
export declare function decorate(text: string, ok: boolean, options: Presentation): string;
|
|
11
|
+
export declare function banner(text: string, options: Presentation): string;
|
|
12
|
+
export declare function backgroundOperation(context: Context, operation: OperationName, args: unknown, label: string, options: Presentation): Promise<Result>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
export function richTerminal(options, env = process.env) {
|
|
3
|
+
return (options.tty &&
|
|
4
|
+
!options.json &&
|
|
5
|
+
!options.plain &&
|
|
6
|
+
options.colour !== false &&
|
|
7
|
+
env.NO_COLOR === undefined &&
|
|
8
|
+
env.TERM !== "dumb" &&
|
|
9
|
+
!env.CI);
|
|
10
|
+
}
|
|
11
|
+
export function decorate(text, ok, options) {
|
|
12
|
+
if (!richTerminal(options))
|
|
13
|
+
return text;
|
|
14
|
+
const [title, ...body] = text.split("\n");
|
|
15
|
+
return `\x1b[${ok ? "32" : "31"}m${ok ? "[ok]" : "[!]"}\x1b[0m \x1b[1m${title}\x1b[0m${body.length ? "\n" + body.join("\n") : ""}`;
|
|
16
|
+
}
|
|
17
|
+
export function banner(text, options) {
|
|
18
|
+
return richTerminal(options)
|
|
19
|
+
? `\x1b[36m+-- INGESTRON ----------------------------------+\x1b[0m\n\n${text}`
|
|
20
|
+
: text;
|
|
21
|
+
}
|
|
22
|
+
export async function backgroundOperation(context, operation, args, label, options) {
|
|
23
|
+
const animate = richTerminal(options) &&
|
|
24
|
+
options.progress !== false &&
|
|
25
|
+
process.env.INGESTRON_NO_ANIMATION !== "1" &&
|
|
26
|
+
!!process.stderr.isTTY;
|
|
27
|
+
const frames = ["|", "/", "-", "\\"];
|
|
28
|
+
let index = 0;
|
|
29
|
+
const safe = label
|
|
30
|
+
.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
31
|
+
.slice(0, Math.max(20, (process.stderr.columns ?? 80) - 8));
|
|
32
|
+
const draw = () => process.stderr.write(`\r\x1b[2K ${frames[index++ % frames.length]} ${safe}`);
|
|
33
|
+
if (animate)
|
|
34
|
+
draw();
|
|
35
|
+
const timer = animate ? setInterval(draw, 100) : undefined;
|
|
36
|
+
try {
|
|
37
|
+
return await new Promise((resolve, reject) => {
|
|
38
|
+
const worker = new Worker(new URL("./operation-worker.js", import.meta.url), { workerData: { context, operation, args } });
|
|
39
|
+
let received = false;
|
|
40
|
+
worker.once("message", (result) => {
|
|
41
|
+
received = true;
|
|
42
|
+
resolve(result);
|
|
43
|
+
});
|
|
44
|
+
worker.once("error", reject);
|
|
45
|
+
worker.once("exit", (code) => {
|
|
46
|
+
if (!received)
|
|
47
|
+
reject(new Error(`Operation worker stopped (${code})`));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (timer)
|
|
53
|
+
clearInterval(timer);
|
|
54
|
+
if (animate)
|
|
55
|
+
process.stderr.write("\r\x1b[2K");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { type Context, type OperationName, type Result } from "@ingestron/core";
|
|
3
|
+
interface Host {
|
|
4
|
+
context: () => Context;
|
|
5
|
+
run: (name: OperationName, args?: any) => void;
|
|
6
|
+
author: (name: OperationName, args: any) => void;
|
|
7
|
+
print: (result: Result, args?: any) => void;
|
|
8
|
+
readData: (file: string) => any;
|
|
9
|
+
canPrompt: () => boolean;
|
|
10
|
+
answer: (value: unknown) => Promise<any>;
|
|
11
|
+
perform: (operation: OperationName, args: any, label: string) => Promise<Result>;
|
|
12
|
+
}
|
|
13
|
+
/** Implemented CLI workflows over the versioned core operation API. */
|
|
14
|
+
export declare function workflowCommands(app: Command, host: Host): void;
|
|
15
|
+
export {};
|