ingestron 0.12.1 → 0.13.1

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.1 — Node.js runtime compatibility
4
+
5
+ - Require Node 22.12 or newer instead of excluding newer major versions. CI tests
6
+ Node 22.12.0, 22.23.2 and 24.12.0, including installed CLI and MCP workflows.
7
+ - Pin core 0.12.1 so the transitive core package no longer warns on Node 24.
8
+
9
+ ## 0.13.0 — simpler plugin installation
10
+
11
+ - CLI and MCP resolve official `github` and `local` names through shared adapter logic.
12
+ Omitted versions and `@latest` select qualified compatible releases, then pin exact
13
+ versions/commits. Repeated installs reuse locks; updates are explicit.
14
+ - Installation now only caches packages. Register project providers separately with
15
+ `plugin configure`; `--cache-only` remains accepted and `install --name` gives a
16
+ migration diagnostic. Existing full references and custom tag prefixes still work.
17
+ - Core remains pinned at 0.12.0; no source/provider runtime change is required.
18
+
3
19
  ## 0.12.1 — project-relative secret files
4
20
 
5
21
  - Use `run --secrets-file .env` instead of `--env-file`. Node interprets the old
package/README.md CHANGED
@@ -10,7 +10,7 @@ the CLI does not install a platform provider or connect to a cloud account.
10
10
 
11
11
  ## Get started
12
12
 
13
- Use Node 22, Git and pnpm 10.15.0. Build the public source:
13
+ Use Node 22.12 or newer, Git and pnpm 10.15.0. Build the public source:
14
14
 
15
15
  ```sh
16
16
  git clone https://github.com/ingestron/cli.git
@@ -29,13 +29,13 @@ To install this checkout's CLI in your terminal:
29
29
 
30
30
  ```sh
31
31
  pnpm pack --pack-destination build/release
32
- npm install --global ./build/release/ingestron-0.12.0.tgz
32
+ npm install --global ./build/release/ingestron-0.13.1.tgz
33
33
  ingestron --version
34
34
  ```
35
35
 
36
36
  This source release prepares the npm package named `ingestron`; it does not claim
37
37
  that the same CLI version is already published on npm. Its core dependency is the
38
- public, exact version `@ingestron/core@0.12.0`.
38
+ public, exact version `@ingestron/core@0.12.1`.
39
39
 
40
40
  Follow the [five-minute quickstart](docs/quickstart.md) for a project you can keep.
41
41
 
@@ -45,12 +45,14 @@ Commands use resource groups such as `source`, `contract`, `flow`, `config` and
45
45
  `plugin`. Installed plugins can add their own namespace:
46
46
 
47
47
  ```text
48
- ingestron plugin install owner/repository@1.2.3
48
+ ingestron plugin install local
49
+ ingestron plugin install github
49
50
  ingestron <namespace> <resource> <verb> --input request.json
50
51
  ```
51
52
 
52
- The repository/version above is a placeholder: use a compatible plugin you can
53
- access. Browse installed packages with `ingestron plugin browse`. Command help
53
+ Installation caches an exact, qualified version without changing project configuration.
54
+ Use `github@1.32.1` for an explicit pin, or `plugin update github` to check for a newer
55
+ qualified release. Browse installed packages with `ingestron plugin browse`. Command help
54
56
  shows the selected plugin's input schema without executing it.
55
57
 
56
58
  - [Command reference](docs/commands.md) — implemented groups, options and errors.
@@ -75,7 +75,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
75
75
  SOFTWARE.
76
76
  ```
77
77
 
78
- ### @ingestron/core@0.12.0
78
+ ### @ingestron/core@0.12.1
79
79
 
80
80
  Declared licence: Apache-2.0
81
81
 
@@ -1,5 +1,5 @@
1
1
  /** Keep package-network work off the presentation thread so progress reflects
2
2
  * actual pending work. This worker uses the same JSON operations as MCP/Web. */
3
3
  import { parentPort, workerData } from "node:worker_threads";
4
- import { execute } from "@ingestron/core";
5
- parentPort.postMessage(execute(workerData.context, workerData.operation, workerData.args));
4
+ import { executePluginOperation } from "./plugin-resolution.js";
5
+ parentPort.postMessage(await executePluginOperation(workerData.context, workerData.operation, workerData.args));
@@ -54,7 +54,7 @@ export function terminal(result, verbose = false, args = {}) {
54
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
55
  .join("\n\n")
56
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");
57
+ "\n\nBrowse versions: ingestron plugin versions github\nGuided install: ingestron plugin install\nInstall: ingestron plugin install github");
58
58
  case "plugin_versions":
59
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
60
  "\n\n" +
@@ -63,7 +63,7 @@ export function terminal(result, verbose = false, args = {}) {
63
63
  case "packages_install":
64
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
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>");
66
+ return lines("Installed plugins", Object.keys(value.packages).map(friendlyReference), "No plugins installed.\nNext: ingestron plugin install local");
67
67
  case "source_list":
68
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
69
  case "source_show":
@@ -0,0 +1,22 @@
1
+ /** Official-name convenience at the adapter boundary; core only receives exact references. */
2
+ import { type Context, type OperationName, type Result } from "@ingestron/core";
3
+ export declare const officialPlugins: {
4
+ readonly github: {
5
+ readonly repository: "ingestron/connectors";
6
+ readonly path: "connectors/github/connector.yaml";
7
+ readonly tagPrefix: "github-";
8
+ };
9
+ readonly local: {
10
+ readonly repository: "ingestron/provider-local";
11
+ readonly path: "plugin/provider.yaml";
12
+ readonly tagPrefix: "";
13
+ };
14
+ };
15
+ export declare function officialName(reference: string): {
16
+ name: keyof typeof officialPlugins;
17
+ version: string;
18
+ } | undefined;
19
+ export declare function catalogueReleases(value: any, name: keyof typeof officialPlugins, host?: string): string[];
20
+ export declare function fetchCatalogue(): Promise<unknown>;
21
+ export declare function resolvePluginArgs(context: Context, operation: OperationName, args: any, catalogue?: typeof fetchCatalogue): Promise<any>;
22
+ export declare function executePluginOperation(context: Context, operation: OperationName, args?: any): Promise<Result>;
@@ -0,0 +1,164 @@
1
+ /** Official-name convenience at the adapter boundary; core only receives exact references. */
2
+ import { executeAsync, operationSchemas, } from "@ingestron/core";
3
+ import { check, packageLock, Problem } from "@ingestron/core/adapter";
4
+ import { coreVersion } from "../version.js";
5
+ export const officialPlugins = {
6
+ github: {
7
+ repository: "ingestron/connectors",
8
+ path: "connectors/github/connector.yaml",
9
+ tagPrefix: "github-",
10
+ },
11
+ local: {
12
+ repository: "ingestron/provider-local",
13
+ path: "plugin/provider.yaml",
14
+ tagPrefix: "",
15
+ },
16
+ };
17
+ const catalogueUrl = "https://raw.githubusercontent.com/ingestron/connectors/main/catalogue.json";
18
+ const stable = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
19
+ const compare = (a, b) => {
20
+ const x = a.split(".").map(Number), y = b.split(".").map(Number);
21
+ return y[0] - x[0] || y[1] - x[1] || y[2] - x[2];
22
+ };
23
+ export function officialName(reference) {
24
+ if (reference.includes("/"))
25
+ return undefined;
26
+ const [name, version, extra] = reference.split("@");
27
+ check(Object.hasOwn(officialPlugins, name), "PACKAGE", `Unknown official plugin '${name}'. Use github, local, or an explicit owner/repository reference.`);
28
+ check(extra === undefined &&
29
+ (version === undefined || version === "latest" || stable.test(version)), "PACKAGE", "Use a stable version, @latest, or omit the version for an official plugin");
30
+ return { name: name, version };
31
+ }
32
+ export function catalogueReleases(value, name, host = coreVersion) {
33
+ const identity = officialPlugins[name];
34
+ check(value?.apiVersion === "ingestron.catalogue/v1", "CATALOGUE", "Unsupported official catalogue format");
35
+ const entry = value.plugins?.[name];
36
+ check(entry &&
37
+ entry.repository === identity.repository &&
38
+ entry.path === identity.path &&
39
+ entry.tagPrefix === identity.tagPrefix &&
40
+ Array.isArray(entry.releases) &&
41
+ entry.releases.length <= 1000, "CATALOGUE", `Invalid official catalogue identity for ${name}`);
42
+ const seen = new Set();
43
+ for (const release of entry.releases) {
44
+ check(stable.test(release.version) &&
45
+ !seen.has(release.version) &&
46
+ Array.isArray(release.coreVersions) &&
47
+ release.coreVersions.length > 0 &&
48
+ release.coreVersions.every((v) => typeof v === "string" && stable.test(v)), "CATALOGUE", `Invalid qualified release for ${name}`);
49
+ seen.add(release.version);
50
+ }
51
+ return entry.releases
52
+ .filter((r) => r.coreVersions.includes(host))
53
+ .map((r) => r.version)
54
+ .sort(compare);
55
+ }
56
+ export async function fetchCatalogue() {
57
+ try {
58
+ const response = await fetch(catalogueUrl, {
59
+ signal: AbortSignal.timeout(10000),
60
+ redirect: "error",
61
+ });
62
+ check(response.ok && response.body, "CATALOGUE", "Official catalogue is unavailable; retry or install an explicit version");
63
+ const reader = response.body.getReader();
64
+ const chunks = [];
65
+ let size = 0;
66
+ try {
67
+ for (;;) {
68
+ const { value, done } = await reader.read();
69
+ if (done)
70
+ break;
71
+ size += value.length;
72
+ check(size <= 262144, "CATALOGUE", "Official catalogue exceeds the size limit");
73
+ chunks.push(value);
74
+ }
75
+ }
76
+ finally {
77
+ await reader.cancel();
78
+ }
79
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
80
+ }
81
+ catch (error) {
82
+ if (error instanceof Problem)
83
+ throw error;
84
+ throw new Problem("CATALOGUE", "Cannot read the official catalogue; retry or install an explicit version");
85
+ }
86
+ }
87
+ export async function resolvePluginArgs(context, operation, args, catalogue = fetchCatalogue) {
88
+ if (operation !== "packages_install" && operation !== "plugin_versions")
89
+ return args;
90
+ const field = operation === "packages_install" ? "reference" : "provider";
91
+ const alias = officialName(args[field]);
92
+ if (!alias)
93
+ return args;
94
+ const identity = officialPlugins[alias.name];
95
+ check(args.tagPrefix === undefined || args.tagPrefix === identity.tagPrefix, "OPTION", "Official plugin tag prefixes cannot be overridden; use an explicit reference for custom tags");
96
+ if (operation === "plugin_versions")
97
+ return {
98
+ ...args,
99
+ provider: identity.repository,
100
+ tagPrefix: identity.tagPrefix,
101
+ };
102
+ // Check permissions before catalogue network access, just as core does before install.
103
+ check(context.allowWrite, "PERMISSION", "Package installation requires write access");
104
+ check(!(args.update && args.frozen), "PACKAGE", "Frozen installation cannot update packages");
105
+ const base = `${identity.repository}/${identity.path}@`;
106
+ let version = alias.version;
107
+ if (version === undefined || version === "latest") {
108
+ const installed = Object.keys(packageLock(context.root).packages)
109
+ .filter((r) => r.startsWith(base) && stable.test(r.slice(base.length)))
110
+ .map((r) => r.slice(base.length))
111
+ .sort(compare);
112
+ if (!args.update && installed.length)
113
+ version = installed[0];
114
+ else {
115
+ check(!args.frozen, "PACKAGE", "Frozen installation requires an existing exact lock; install an exact version first");
116
+ check(context.allowNetwork, "PERMISSION", "Latest plugin resolution requires explicit package-network access");
117
+ check(!args.fromGit, "OPTION", "Use an exact version with --from-git; latest selects official qualified releases");
118
+ const versions = catalogueReleases(await catalogue(), alias.name);
119
+ check(versions.length, "COMPATIBILITY", `No qualified ${alias.name} release supports core ${coreVersion}; update the CLI or select an explicitly compatible version`);
120
+ version = [...versions, ...installed].sort(compare)[0];
121
+ }
122
+ }
123
+ // Even 'update' must never retarget an existing immutable version lock.
124
+ return {
125
+ ...args,
126
+ reference: `${base}${version}`,
127
+ tagPrefix: identity.tagPrefix,
128
+ update: false,
129
+ };
130
+ }
131
+ export async function executePluginOperation(context, operation, args = {}) {
132
+ try {
133
+ if (operation === "packages_install" || operation === "plugin_versions") {
134
+ const parsed = operationSchemas[operation].safeParse(args);
135
+ check(parsed.success, "SCHEMA", "Invalid plugin operation arguments");
136
+ if (operation === "packages_install")
137
+ check(context.allowWrite, "PERMISSION", "Package installation requires write access");
138
+ check(context.allowNetwork || parsed.data?.fromGit, "PERMISSION", "Package access requires explicit package-network access");
139
+ args = await resolvePluginArgs(context, operation, parsed.data);
140
+ }
141
+ const result = await executeAsync(context, operation, args);
142
+ // Keep custom manifest paths in every returned installable reference.
143
+ if (operation === "plugin_versions" && result.ok) {
144
+ const identity = Object.values(officialPlugins).find((p) => p.repository === args.provider && p.tagPrefix === args.tagPrefix);
145
+ if (identity)
146
+ result.result.versions = result.result.versions.map((v) => ({
147
+ ...v,
148
+ reference: `${identity.repository}/${identity.path}@${v.version}`,
149
+ }));
150
+ }
151
+ return result;
152
+ }
153
+ catch (error) {
154
+ const e = error instanceof Problem
155
+ ? error
156
+ : new Problem("PACKAGE", "Plugin resolution failed; check the reference and catalogue");
157
+ return {
158
+ apiVersion: "ingestron.operation/v1",
159
+ operation,
160
+ ok: false,
161
+ diagnostics: [{ code: e.code, message: e.message }],
162
+ };
163
+ }
164
+ }
@@ -1,10 +1,7 @@
1
+ import { officialName } from "./plugin-resolution.js";
1
2
  import { browsePlugins } from "./plugin-browser.js";
2
3
  import * as prompts from "@clack/prompts";
3
4
  import { friendlyReference } from "@ingestron/core/adapter";
4
- import { existsSync } from "node:fs";
5
- import { resolve } from "node:path";
6
- import { packageYaml, resolvePackage } from "@ingestron/core/adapter";
7
- import { execute, } from "@ingestron/core";
8
5
  import { check } from "@ingestron/core/adapter";
9
6
  /** Implemented CLI workflows over the versioned core operation API. */
10
7
  export function workflowCommands(app, host) {
@@ -179,16 +176,19 @@ export function workflowCommands(app, host) {
179
176
  .action((id) => run("run_status", { id }));
180
177
  // Installation is explicit; browsing only inspects installed packages.
181
178
  const installProvider = async (reference, options, update = false) => {
182
- if (!reference || !reference.includes("@")) {
183
- check(canPrompt(), "INPUT", "Choose an exact reference, e.g. plugin install owner/repository@1.2.3. Use plugin versions owner/repository to inspect tags.");
179
+ if (!reference) {
180
+ check(canPrompt(), "INPUT", "Choose an official plugin: plugin install github or plugin install local; explicit owner/repository@version references also work.");
181
+ reference = await answer(prompts.text({
182
+ message: "Official plugin name or owner/repository",
183
+ validate: (value) => /^(github|local|[\w.-]+\/[\w.-]+)$/.test(value ?? "")
184
+ ? undefined
185
+ : "Use github, local or owner/repository",
186
+ }));
187
+ }
188
+ check(typeof reference === "string", "INPUT", "Choose a plugin reference");
189
+ if (reference.includes("/") && !reference.includes("@")) {
190
+ check(canPrompt(), "INPUT", "Explicit repositories require an exact version: owner/repository@1.2.3. Official names github and local can omit the version.");
184
191
  check(!options.frozen, "OPTION", "Frozen installation requires an exact locked reference");
185
- if (!reference)
186
- reference = await answer(prompts.text({
187
- message: "Plugin GitHub repository (owner/repository)",
188
- validate: (value) => /^[\w.-]+\/[\w.-]+$/.test(value ?? "")
189
- ? undefined
190
- : "Use owner/repository",
191
- }));
192
192
  const available = await perform("plugin_versions", {
193
193
  provider: reference,
194
194
  ...(options.fromGit ? { fromGit: options.fromGit } : {}),
@@ -210,6 +210,8 @@ export function workflowCommands(app, host) {
210
210
  }));
211
211
  }
212
212
  check(typeof reference === "string", "INPUT", "Choose a provider version");
213
+ officialName(reference);
214
+ check(!options.name, "OPTION", "Installation no longer configures projects. Use plugin configure <exact-reference> --name <configuration>.");
213
215
  const result = await perform("packages_install", {
214
216
  reference,
215
217
  update,
@@ -221,41 +223,7 @@ export function workflowCommands(app, host) {
221
223
  print(result);
222
224
  return;
223
225
  }
224
- const provider = [
225
- "ingestron.provider/v1",
226
- "ingestron.connector/v1",
227
- ].includes(packageYaml(resolvePackage(context().root, result.result.reference).file)
228
- .apiVersion);
229
- if (options.cacheOnly ||
230
- !provider ||
231
- !existsSync(resolve(context().root, "project.yaml"))) {
232
- print(result, { reference });
233
- return;
234
- }
235
- const proposal = execute(context(), "plugin_configure", {
236
- reference,
237
- update,
238
- ...(options.name ? { name: options.name } : {}),
239
- });
240
- if (!proposal.ok) {
241
- print(proposal);
242
- return;
243
- }
244
- const applied = execute(context(), "apply", {
245
- proposal: proposal.result,
246
- });
247
- if (!applied.ok) {
248
- print(applied);
249
- return;
250
- }
251
- print({
252
- ...result,
253
- result: {
254
- ...result.result,
255
- configured: true,
256
- files: applied.result.files,
257
- },
258
- }, { reference });
226
+ print(result, { reference: result.result.reference });
259
227
  };
260
228
  const plugin = app
261
229
  .command("plugin")
@@ -288,7 +256,7 @@ export function workflowCommands(app, host) {
288
256
  .action((id) => run("plugin_show", { id }));
289
257
  plugin
290
258
  .command("versions <provider>")
291
- .description("List exact version tags from GitHub; supports owner/repository")
259
+ .description("List stable tags for an official name or owner/repository; compatibility checked at install")
292
260
  .option("--from-git <directory>", "Inspect a local Git repository")
293
261
  .option("--tag-prefix <prefix>", "Component tag prefix in a shared repository")
294
262
  .action(async (provider, options) => print(await perform("plugin_versions", { provider, ...options }, `Finding versions for ${provider}`)));
@@ -299,12 +267,12 @@ export function workflowCommands(app, host) {
299
267
  for (const update of [false, true])
300
268
  plugin
301
269
  .command(`${update ? "update <reference>" : "install [reference]"}`)
302
- .description("Install an exact version or choose interactively; register provider defaults")
270
+ .description("Cache a plugin without changing project configuration; official names default to latest qualified")
303
271
  .option("--from-git <directory>", "Local Git source")
304
272
  .option("--tag-prefix <prefix>", "Component tag prefix in a shared repository")
305
273
  .option("--frozen", "Require an existing immutable lock")
306
- .option("--name <configuration>", "Provider configuration name (default: platform name)")
307
- .option("--cache-only", "Install without changing project provider configuration")
274
+ .option("--name <configuration>", "Removed: use plugin configure --name instead")
275
+ .option("--cache-only", "Compatibility option; cache-only is now the default")
308
276
  .action((reference, options) => installProvider(reference, options, update));
309
277
  plugin
310
278
  .command("configure <reference>")
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { execute, executeAsync, operationSchemas, } from "@ingestron/core";
3
+ import { execute, operationSchemas } from "@ingestron/core";
4
+ import { executePluginOperation } from "../cli/plugin-resolution.js";
4
5
  import { version } from "../version.js";
5
6
  export function createServer(root, allowWrite = false, environment = "dev", allowNetwork = false, allowExecute = false) {
6
7
  const server = new McpServer({ name: "ingestron", version }, {
@@ -32,7 +33,7 @@ export function createServer(root, allowWrite = false, environment = "dev", allo
32
33
  ["plugin_versions", "packages_install"].includes(name)),
33
34
  },
34
35
  }, async (request) => {
35
- const result = await executeAsync({ root, environment, allowWrite, allowNetwork, allowExecute }, name, request);
36
+ const result = await executePluginOperation({ root, environment, allowWrite, allowNetwork, allowExecute }, name, request);
36
37
  return {
37
38
  content: [{ type: "text", text: JSON.stringify(result) }],
38
39
  isError: !result.ok,
@@ -1 +1,2 @@
1
1
  export declare const version: string;
2
+ export declare const coreVersion: string;
@@ -11,3 +11,4 @@ while (!existsSync(join(root, "package.json")) ||
11
11
  root = parent;
12
12
  }
13
13
  export const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
14
+ export const coreVersion = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).dependencies["@ingestron/core"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ingestron",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "description": "Build repeatable data pipelines from reviewed contracts and versioned standards.",
6
6
  "license": "Apache-2.0",
@@ -20,11 +20,11 @@
20
20
  "THIRD_PARTY_NOTICES.md"
21
21
  ],
22
22
  "engines": {
23
- "node": ">=22 <23"
23
+ "node": ">=22.12.0"
24
24
  },
25
25
  "dependencies": {
26
26
  "@clack/prompts": "1.7.0",
27
- "@ingestron/core": "0.12.0",
27
+ "@ingestron/core": "0.12.1",
28
28
  "@modelcontextprotocol/sdk": "1.30.0",
29
29
  "commander": "15.0.0",
30
30
  "yaml": "2.9.0"