@apso/cli 0.38.4 → 0.39.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/README.md +11 -0
- package/dist/commands/mcp/serve.js +5 -0
- package/dist/commands/schema/lint.d.ts +18 -0
- package/dist/commands/schema/lint.js +74 -0
- package/dist/commands/schema/validate.js +20 -0
- package/dist/lib/apsorc-parser.d.ts +13 -8
- package/dist/lib/apsorc-parser.js +27 -52
- package/dist/lib/mcp-schema-tools.d.ts +31 -0
- package/dist/lib/mcp-schema-tools.js +69 -0
- package/npm-shrinkwrap.json +12 -2
- package/oclif.manifest.json +31 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -57,6 +57,17 @@ Add to your MCP configuration:
|
|
|
57
57
|
| `start_dev_server` | Start local dev environment via Docker Compose |
|
|
58
58
|
| `deploy_api` | Deploy to the Apso platform |
|
|
59
59
|
|
|
60
|
+
**Schema edit tools** (from `@apso/schema-tools`) change the project's `.apsorc` one step at a time. Each write is linted with the rules `apso generate` enforces; a change that would add a lint error is rejected and the file is left as it was.
|
|
61
|
+
|
|
62
|
+
| Tool | Description |
|
|
63
|
+
|------|-------------|
|
|
64
|
+
| `get_schema`, `describe_entity` | Read the schema outline, or one entity and its relationships |
|
|
65
|
+
| `add_entity`, `rename_entity`, `remove_entity` | Entities (relationships and foreign key indexes follow renames and removals) |
|
|
66
|
+
| `add_field`, `update_field`, `rename_field`, `remove_field` | Fields (indexes and uniques follow renames and removals) |
|
|
67
|
+
| `add_relationship`, `remove_relationship` | Relationships |
|
|
68
|
+
| `add_index` | Index or unique index |
|
|
69
|
+
| `lint_schema` | Lint report |
|
|
70
|
+
|
|
60
71
|
**Available resources:**
|
|
61
72
|
|
|
62
73
|
| Resource | URI | Description |
|
|
@@ -16,6 +16,7 @@ const lib_1 = require("../../lib");
|
|
|
16
16
|
const runner_1 = require("../../lib/doctor/runner");
|
|
17
17
|
const file_system_1 = require("../../lib/utils/file-system");
|
|
18
18
|
const schema_convert_1 = require("../../lib/utils/schema-convert");
|
|
19
|
+
const mcp_schema_tools_1 = require("../../lib/mcp-schema-tools");
|
|
19
20
|
const perf_hooks_1 = require("perf_hooks");
|
|
20
21
|
class McpServe extends base_command_1.default {
|
|
21
22
|
async run() {
|
|
@@ -69,6 +70,10 @@ class McpServe extends base_command_1.default {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
registerTools(server) {
|
|
73
|
+
// ── granular schema edits (@apso/schema-tools) ─────────────────
|
|
74
|
+
// get_schema, describe_entity, add_entity, add_field, rename_field, ...
|
|
75
|
+
// on the project's .apsorc, each linted before it is written.
|
|
76
|
+
(0, mcp_schema_tools_1.registerSchemaTools)(server);
|
|
72
77
|
// ── design_schema ──────────────────────────────────────────────
|
|
73
78
|
server.tool("design_schema", "Design a database schema from application requirements. Takes a description of entities, relationships, and business rules. Returns a validated .apsorc schema definition ready for code generation.", {
|
|
74
79
|
requirements: zod_1.z
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LintIssue, LintResult } from "@apso/schema-tools";
|
|
2
|
+
import BaseCommand from "../../lib/base-command";
|
|
3
|
+
import { ApsorcType } from "../../lib/apsorc-parser";
|
|
4
|
+
/** Lints (and with `fix`, repairs and saves) an .apsorc file. */
|
|
5
|
+
export declare function lintApsorc(configPath: string, apsorc: ApsorcType, fix: boolean): {
|
|
6
|
+
result: LintResult;
|
|
7
|
+
fixed: LintIssue[];
|
|
8
|
+
backupPath?: string;
|
|
9
|
+
};
|
|
10
|
+
export default class SchemaLint extends BaseCommand {
|
|
11
|
+
static description: string;
|
|
12
|
+
static examples: string[];
|
|
13
|
+
static flags: {
|
|
14
|
+
fix: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lintApsorc = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
+
const path = tslib_1.__importStar(require("path"));
|
|
8
|
+
const schema_tools_1 = require("@apso/schema-tools");
|
|
9
|
+
const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
|
|
10
|
+
const config_1 = require("../../lib/config");
|
|
11
|
+
const apsorc_parser_1 = require("../../lib/apsorc-parser");
|
|
12
|
+
/** Lints (and with `fix`, repairs and saves) an .apsorc file. */
|
|
13
|
+
function lintApsorc(configPath, apsorc, fix) {
|
|
14
|
+
if (!fix)
|
|
15
|
+
return { result: (0, schema_tools_1.lintSchema)(apsorc), fixed: [] };
|
|
16
|
+
const { schema, applied } = (0, schema_tools_1.fixSchema)(apsorc);
|
|
17
|
+
let backupPath;
|
|
18
|
+
if (applied.length > 0) {
|
|
19
|
+
backupPath = (0, config_1.createBackup)(configPath, path.dirname(configPath));
|
|
20
|
+
fs.writeFileSync(configPath, `${JSON.stringify(schema, null, 2)}\n`, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
return { result: (0, schema_tools_1.lintSchema)(schema), fixed: applied, backupPath };
|
|
23
|
+
}
|
|
24
|
+
exports.lintApsorc = lintApsorc;
|
|
25
|
+
class SchemaLint extends base_command_1.default {
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(SchemaLint);
|
|
28
|
+
let file;
|
|
29
|
+
try {
|
|
30
|
+
file = (0, apsorc_parser_1.readApsorcFile)();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
this.error(`Failed to read .apsorc: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
if (file.apsorc.version !== 2) {
|
|
36
|
+
this.error("apso schema lint supports version 2 .apsorc files only.");
|
|
37
|
+
}
|
|
38
|
+
const { result, fixed, backupPath } = lintApsorc(file.configPath, file.apsorc, flags.fix);
|
|
39
|
+
if (flags.json) {
|
|
40
|
+
this.log(JSON.stringify({ ...result, fixed }, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (fixed.length > 0) {
|
|
44
|
+
this.log(`Fixed ${fixed.length} issue(s) in ${file.configPath} (backup: ${backupPath}):`);
|
|
45
|
+
for (const issue of fixed)
|
|
46
|
+
this.log(` - ${issue.rule}: ${issue.message}`);
|
|
47
|
+
this.log("");
|
|
48
|
+
}
|
|
49
|
+
this.log((0, schema_tools_1.formatLintReport)(result));
|
|
50
|
+
if (!flags.fix && result.issues.some((issue) => issue.fixable)) {
|
|
51
|
+
this.log('\nRun "apso schema lint --fix" to apply the auto-fixable changes.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (result.errorCount > 0)
|
|
55
|
+
this.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
SchemaLint.description = "Check the local .apsorc for names and settings that would break the generated code";
|
|
59
|
+
SchemaLint.examples = [
|
|
60
|
+
"$ apso schema lint",
|
|
61
|
+
"$ apso schema lint --fix",
|
|
62
|
+
"$ apso schema lint --json",
|
|
63
|
+
];
|
|
64
|
+
SchemaLint.flags = {
|
|
65
|
+
fix: core_1.Flags.boolean({
|
|
66
|
+
description: "Apply the safe automatic fixes and save .apsorc (a backup is kept)",
|
|
67
|
+
default: false,
|
|
68
|
+
}),
|
|
69
|
+
json: core_1.Flags.boolean({
|
|
70
|
+
description: "Print the result as JSON",
|
|
71
|
+
default: false,
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
exports.default = SchemaLint;
|
|
@@ -5,12 +5,32 @@ const core_1 = require("@oclif/core");
|
|
|
5
5
|
const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
|
|
6
6
|
const config_1 = require("../../lib/config");
|
|
7
7
|
const services_1 = require("../../lib/api/services");
|
|
8
|
+
const schema_tools_1 = require("@apso/schema-tools");
|
|
8
9
|
const apsorc_parser_1 = require("../../lib/apsorc-parser");
|
|
9
10
|
const schema_convert_1 = require("../../lib/utils/schema-convert");
|
|
10
11
|
class SchemaValidate extends base_command_1.default {
|
|
11
12
|
async run() {
|
|
12
13
|
var _a;
|
|
13
14
|
const { flags } = await this.parse(SchemaValidate);
|
|
15
|
+
// Lint first so every issue is listed, not just the first one parseApsorc throws on
|
|
16
|
+
let file;
|
|
17
|
+
try {
|
|
18
|
+
file = (0, apsorc_parser_1.readApsorcFile)();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
22
|
+
this.error(`Failed to read .apsorc: ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
if (file.apsorc.version === 2) {
|
|
25
|
+
const lint = (0, schema_tools_1.lintSchema)(file.apsorc);
|
|
26
|
+
if (lint.issues.length > 0) {
|
|
27
|
+
this.log((0, schema_tools_1.formatLintReport)(lint));
|
|
28
|
+
this.log("");
|
|
29
|
+
}
|
|
30
|
+
if (lint.errorCount > 0) {
|
|
31
|
+
this.error(`Validation failed with ${lint.errorCount} lint error(s). Run "apso schema lint --fix" to apply the safe fixes.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
14
34
|
// Parse .apsorc
|
|
15
35
|
let parsed;
|
|
16
36
|
try {
|
|
@@ -52,8 +52,9 @@ type ParsedApsorc = {
|
|
|
52
52
|
};
|
|
53
53
|
export declare const parseApsorcV1: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
54
54
|
/**
|
|
55
|
-
* A
|
|
56
|
-
*
|
|
55
|
+
* A schema problem that would make `apso generate` emit code that does not
|
|
56
|
+
* compile. `code` is the @apso/schema-tools rule id (FIELD_RELATIONSHIP_COLLISION,
|
|
57
|
+
* DUPLICATE_FIELD_NAME, ...), the same codes the apso-client-v2 validator used.
|
|
57
58
|
*/
|
|
58
59
|
export declare class ApsorcNamingError extends Error {
|
|
59
60
|
code: string;
|
|
@@ -62,13 +63,12 @@ export declare class ApsorcNamingError extends Error {
|
|
|
62
63
|
constructor(message: string, code: string, entity: string, field?: string | undefined);
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
|
-
* Throws an ApsorcNamingError
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* run test/fixtures/codegen-naming-cases.json, so change them together.
|
|
66
|
+
* Throws an ApsorcNamingError for the first @apso/schema-tools error, e.g. a
|
|
67
|
+
* text field `notes` on Contact plus Note ManyToOne Contact, whose inverse
|
|
68
|
+
* side is `notes: Note[]` on Contact (TS2300 Duplicate identifier). The client
|
|
69
|
+
* schema builder runs the same rules, so a schema that passes there passes here.
|
|
70
70
|
*/
|
|
71
|
-
export declare const assertCodegenSafeNames: (
|
|
71
|
+
export declare const assertCodegenSafeNames: (apsorc: Pick<ApsorcType, "entities" | "relationships">) => void;
|
|
72
72
|
export declare const parseApsorcV2: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
73
73
|
export declare const parseApsorc: () => ParsedApsorc;
|
|
74
74
|
/**
|
|
@@ -76,5 +76,10 @@ export declare const parseApsorc: () => ParsedApsorc;
|
|
|
76
76
|
* Searches upwards from the current working directory.
|
|
77
77
|
* @returns The absolute path to the .apsorc file, or null if not found.
|
|
78
78
|
*/
|
|
79
|
+
/** Reads the .apsorc file itself (not rc-merged), for commands that rewrite it. */
|
|
80
|
+
export declare const readApsorcFile: () => {
|
|
81
|
+
configPath: string;
|
|
82
|
+
apsorc: ApsorcType;
|
|
83
|
+
};
|
|
79
84
|
export declare const findConfigPath: () => string | null;
|
|
80
85
|
export {};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.findConfigPath = exports.parseApsorc = exports.parseApsorcV2 = exports.assertCodegenSafeNames = exports.ApsorcNamingError = exports.parseApsorcV1 = exports.ApiType = void 0;
|
|
3
|
+
exports.findConfigPath = exports.readApsorcFile = exports.parseApsorc = exports.parseApsorcV2 = exports.assertCodegenSafeNames = exports.ApsorcNamingError = exports.parseApsorcV1 = exports.ApiType = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs = tslib_1.__importStar(require("fs"));
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
7
|
const rc_1 = tslib_1.__importDefault(require("rc"));
|
|
8
8
|
const relationships_1 = require("./utils/relationships");
|
|
9
9
|
const perf_hooks_1 = require("perf_hooks");
|
|
10
|
-
|
|
11
|
-
const
|
|
10
|
+
// @apso/schema-tools is published on npm (apsoai/apso-packages).
|
|
11
|
+
const schema_tools_1 = require("@apso/schema-tools");
|
|
12
12
|
var ApiType;
|
|
13
13
|
(function (ApiType) {
|
|
14
14
|
ApiType["Graphql"] = "graphql";
|
|
@@ -23,11 +23,10 @@ const parseApsorcV1 = (apsorc) => {
|
|
|
23
23
|
return { entities, relationshipMap };
|
|
24
24
|
};
|
|
25
25
|
exports.parseApsorcV1 = parseApsorcV1;
|
|
26
|
-
/** Valid in TypeScript, Python and Go, so every target language compiles it. */
|
|
27
|
-
const IDENTIFIER = /^[A-Z_a-z]\w*$/;
|
|
28
26
|
/**
|
|
29
|
-
* A
|
|
30
|
-
*
|
|
27
|
+
* A schema problem that would make `apso generate` emit code that does not
|
|
28
|
+
* compile. `code` is the @apso/schema-tools rule id (FIELD_RELATIONSHIP_COLLISION,
|
|
29
|
+
* DUPLICATE_FIELD_NAME, ...), the same codes the apso-client-v2 validator used.
|
|
31
30
|
*/
|
|
32
31
|
class ApsorcNamingError extends Error {
|
|
33
32
|
constructor(message, code, entity, field) {
|
|
@@ -40,57 +39,24 @@ class ApsorcNamingError extends Error {
|
|
|
40
39
|
}
|
|
41
40
|
exports.ApsorcNamingError = ApsorcNamingError;
|
|
42
41
|
/**
|
|
43
|
-
* Throws an ApsorcNamingError
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* run test/fixtures/codegen-naming-cases.json, so change them together.
|
|
42
|
+
* Throws an ApsorcNamingError for the first @apso/schema-tools error, e.g. a
|
|
43
|
+
* text field `notes` on Contact plus Note ManyToOne Contact, whose inverse
|
|
44
|
+
* side is `notes: Note[]` on Contact (TS2300 Duplicate identifier). The client
|
|
45
|
+
* schema builder runs the same rules, so a schema that passes there passes here.
|
|
48
46
|
*/
|
|
49
|
-
const assertCodegenSafeNames = (
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!IDENTIFIER.test(entity.name)) {
|
|
57
|
-
throw new ApsorcNamingError(`Entity "${entity.name}" is not a valid name. Use letters, digits and underscores, starting with a letter.`, "INVALID_ENTITY_NAME", entity.name);
|
|
58
|
-
}
|
|
59
|
-
const fieldNames = new Set();
|
|
60
|
-
for (const { name } of entity.fields || []) {
|
|
61
|
-
if (!IDENTIFIER.test(name)) {
|
|
62
|
-
throw new ApsorcNamingError(`Field "${name}" on entity "${entity.name}" is not a valid name. Use letters, digits and underscores.`, "INVALID_FIELD_NAME", entity.name, name);
|
|
63
|
-
}
|
|
64
|
-
if (fieldNames.has(name)) {
|
|
65
|
-
throw new ApsorcNamingError(`Entity "${entity.name}" has more than one field named "${name}". Remove or rename one in .apsorc.`, "DUPLICATE_FIELD_NAME", entity.name, name);
|
|
66
|
-
}
|
|
67
|
-
fieldNames.add(name);
|
|
68
|
-
}
|
|
69
|
-
// Same property names and dedupe key as getRelationshipForTemplate + entity.eta.
|
|
70
|
-
const generated = new Map();
|
|
71
|
-
for (const rel of relationshipMap[entity.name] || []) {
|
|
72
|
-
const base = rel.referenceName || rel.name;
|
|
73
|
-
const property = (0, casing_1.camelCase)(rel.type === "OneToMany" || rel.type === "ManyToMany"
|
|
74
|
-
? (0, pluralize_1.default)(base)
|
|
75
|
-
: base);
|
|
76
|
-
const key = `${rel.name}:${rel.referenceName || "default"}`;
|
|
77
|
-
const desc = `${rel.type} relationship to "${rel.name}"`;
|
|
78
|
-
if (fieldNames.has(property)) {
|
|
79
|
-
throw new ApsorcNamingError(`Entity "${entity.name}" has a field "${property}" with the same name as the property generated by its ${desc}. Rename the field in .apsorc.`, "FIELD_RELATIONSHIP_COLLISION", entity.name, property);
|
|
80
|
-
}
|
|
81
|
-
const existing = generated.get(property);
|
|
82
|
-
if (existing && existing.key !== key) {
|
|
83
|
-
throw new ApsorcNamingError(`Entity "${entity.name}" gets the property "${property}" from both its ${existing.rel} and its ${desc}. Give one of them a different "to_name" in .apsorc.`, "RELATIONSHIP_PROPERTY_CLASH", entity.name, property);
|
|
84
|
-
}
|
|
85
|
-
generated.set(property, { key, rel: desc });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
47
|
+
const assertCodegenSafeNames = (apsorc) => {
|
|
48
|
+
const { issues } = (0, schema_tools_1.lintSchema)(apsorc);
|
|
49
|
+
const error = issues.find((issue) => issue.severity === "error");
|
|
50
|
+
if (!error)
|
|
51
|
+
return;
|
|
52
|
+
const more = issues.filter((issue) => issue.severity === "error").length - 1;
|
|
53
|
+
throw new ApsorcNamingError(`${error.message}${more > 0 ? ` (${more} more error${more === 1 ? "" : "s"})` : ""} Run "apso schema lint" to see every issue, or "apso schema lint --fix" to apply the safe fixes.`, error.rule, error.entity || "", error.field);
|
|
88
54
|
};
|
|
89
55
|
exports.assertCodegenSafeNames = assertCodegenSafeNames;
|
|
90
56
|
const parseApsorcV2 = (apsorc) => {
|
|
91
57
|
const { entities, relationships: apsoRelationships } = apsorc;
|
|
58
|
+
(0, exports.assertCodegenSafeNames)(apsorc);
|
|
92
59
|
const relationshipMap = (0, relationships_1.parseRelationships)(apsoRelationships);
|
|
93
|
-
(0, exports.assertCodegenSafeNames)(entities, relationshipMap);
|
|
94
60
|
return { entities, relationshipMap };
|
|
95
61
|
};
|
|
96
62
|
exports.parseApsorcV2 = parseApsorcV2;
|
|
@@ -182,6 +148,15 @@ exports.parseApsorc = parseApsorc;
|
|
|
182
148
|
* Searches upwards from the current working directory.
|
|
183
149
|
* @returns The absolute path to the .apsorc file, or null if not found.
|
|
184
150
|
*/
|
|
151
|
+
/** Reads the .apsorc file itself (not rc-merged), for commands that rewrite it. */
|
|
152
|
+
const readApsorcFile = () => {
|
|
153
|
+
const configPath = (0, exports.findConfigPath)();
|
|
154
|
+
if (!configPath) {
|
|
155
|
+
throw new Error("No .apsorc found in this directory or any parent.");
|
|
156
|
+
}
|
|
157
|
+
return { configPath, apsorc: JSON.parse(fs.readFileSync(configPath).toString()) };
|
|
158
|
+
};
|
|
159
|
+
exports.readApsorcFile = readApsorcFile;
|
|
185
160
|
const findConfigPath = () => {
|
|
186
161
|
let currentDir = process.cwd();
|
|
187
162
|
while (currentDir !== path.parse(currentDir).root) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface SchemaToolFileResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
message: string;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
configPath: string;
|
|
6
|
+
/** True when the operation changed the schema and .apsorc was rewritten. */
|
|
7
|
+
saved: boolean;
|
|
8
|
+
lint: {
|
|
9
|
+
errorCount: number;
|
|
10
|
+
warningCount: number;
|
|
11
|
+
report: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Run one @apso/schema-tools operation on an .apsorc file: read it, apply the
|
|
16
|
+
* operation, write it back when the operation changed the schema, and return
|
|
17
|
+
* the lint result. An operation that would add a lint error is rejected by
|
|
18
|
+
* the tool and the file is left untouched.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applySchemaToolToFile(configPath: string, name: string, input: unknown): SchemaToolFileResult;
|
|
21
|
+
type ToolServer = {
|
|
22
|
+
tool: (...args: any[]) => unknown;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Register every @apso/schema-tools operation as a granular MCP tool on the
|
|
26
|
+
* project's .apsorc (found from the current directory up), next to the coarse
|
|
27
|
+
* design/validate/scaffold tools. The JSON Schema inputs from the package are
|
|
28
|
+
* converted to zod for the MCP SDK.
|
|
29
|
+
*/
|
|
30
|
+
export declare function registerSchemaTools(server: ToolServer): void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSchemaTools = exports.applySchemaToolToFile = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const schema_tools_1 = require("@apso/schema-tools");
|
|
8
|
+
const apsorc_parser_1 = require("./apsorc-parser");
|
|
9
|
+
/**
|
|
10
|
+
* Run one @apso/schema-tools operation on an .apsorc file: read it, apply the
|
|
11
|
+
* operation, write it back when the operation changed the schema, and return
|
|
12
|
+
* the lint result. An operation that would add a lint error is rejected by
|
|
13
|
+
* the tool and the file is left untouched.
|
|
14
|
+
*/
|
|
15
|
+
function applySchemaToolToFile(configPath, name, input) {
|
|
16
|
+
// eslint-disable-next-line unicorn/prefer-json-parse-buffer -- JSON.parse's TS type requires a string
|
|
17
|
+
const apsorc = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
18
|
+
if (apsorc.version !== 2) {
|
|
19
|
+
throw new Error("The schema tools support version 2 .apsorc files only.");
|
|
20
|
+
}
|
|
21
|
+
const result = (0, schema_tools_1.runSchemaTool)(apsorc, name, input);
|
|
22
|
+
const saved = result.ok && result.schema !== apsorc;
|
|
23
|
+
if (saved)
|
|
24
|
+
fs.writeFileSync(configPath, `${JSON.stringify(result.schema, null, 2)}\n`, "utf-8");
|
|
25
|
+
const lint = (0, schema_tools_1.lintSchema)(result.schema);
|
|
26
|
+
return {
|
|
27
|
+
ok: result.ok,
|
|
28
|
+
message: result.message,
|
|
29
|
+
...(result.data === undefined ? {} : { data: result.data }),
|
|
30
|
+
configPath,
|
|
31
|
+
saved,
|
|
32
|
+
lint: { errorCount: lint.errorCount, warningCount: lint.warningCount, report: (0, schema_tools_1.formatLintReport)(lint) },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
exports.applySchemaToolToFile = applySchemaToolToFile;
|
|
36
|
+
/**
|
|
37
|
+
* Register every @apso/schema-tools operation as a granular MCP tool on the
|
|
38
|
+
* project's .apsorc (found from the current directory up), next to the coarse
|
|
39
|
+
* design/validate/scaffold tools. The JSON Schema inputs from the package are
|
|
40
|
+
* converted to zod for the MCP SDK.
|
|
41
|
+
*/
|
|
42
|
+
function registerSchemaTools(server) {
|
|
43
|
+
for (const t of schema_tools_1.schemaTools) {
|
|
44
|
+
const shape = zod_1.z.fromJSONSchema(t.inputSchema).shape;
|
|
45
|
+
server.tool(t.name, `${t.description} Operates on the .apsorc in the current project.${t.readOnly ? "" : " Writes the file only when the change lints clean of new errors."}`, shape, { readOnlyHint: t.readOnly }, async (input) => {
|
|
46
|
+
const configPath = (0, apsorc_parser_1.findConfigPath)();
|
|
47
|
+
if (!configPath) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: "No .apsorc found in this directory or its parents. Run `apso init` first." }],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const result = applySchemaToolToFile(configPath, t.name, input);
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
57
|
+
isError: !result.ok,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.registerSchemaTools = registerSchemaTools;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@apso/cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.39.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@apso/schema-tools": "^0.1.0",
|
|
12
13
|
"@biomejs/biome": "^1.9.4",
|
|
13
14
|
"@electric-sql/pglite": "^0.2.17",
|
|
14
15
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -147,6 +148,15 @@
|
|
|
147
148
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
|
148
149
|
"license": "MIT"
|
|
149
150
|
},
|
|
151
|
+
"node_modules/@apso/schema-tools": {
|
|
152
|
+
"version": "0.1.0",
|
|
153
|
+
"resolved": "https://registry.npmjs.org/@apso/schema-tools/-/schema-tools-0.1.0.tgz",
|
|
154
|
+
"integrity": "sha512-c/vvsyo+ZMxq4DK4V1UlP3N/De9ijTfcAM/9VrZHmLxeA6JiXx3J+baSBAseffBBTUeNYDp4D/c4iLz8CcCePw==",
|
|
155
|
+
"license": "Apache-2.0",
|
|
156
|
+
"dependencies": {
|
|
157
|
+
"pluralize": "^8.0.0"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
150
160
|
"node_modules/@babel/code-frame": {
|
|
151
161
|
"version": "7.21.4",
|
|
152
162
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.39.0",
|
|
3
3
|
"commands": {
|
|
4
4
|
"config": {
|
|
5
5
|
"id": "config",
|
|
@@ -784,6 +784,36 @@
|
|
|
784
784
|
"flags": {},
|
|
785
785
|
"args": {}
|
|
786
786
|
},
|
|
787
|
+
"schema:lint": {
|
|
788
|
+
"id": "schema:lint",
|
|
789
|
+
"description": "Check the local .apsorc for names and settings that would break the generated code",
|
|
790
|
+
"strict": true,
|
|
791
|
+
"pluginName": "@apso/cli",
|
|
792
|
+
"pluginAlias": "@apso/cli",
|
|
793
|
+
"pluginType": "core",
|
|
794
|
+
"aliases": [],
|
|
795
|
+
"hiddenAliases": [],
|
|
796
|
+
"examples": [
|
|
797
|
+
"$ apso schema lint",
|
|
798
|
+
"$ apso schema lint --fix",
|
|
799
|
+
"$ apso schema lint --json"
|
|
800
|
+
],
|
|
801
|
+
"flags": {
|
|
802
|
+
"fix": {
|
|
803
|
+
"name": "fix",
|
|
804
|
+
"type": "boolean",
|
|
805
|
+
"description": "Apply the safe automatic fixes and save .apsorc (a backup is kept)",
|
|
806
|
+
"allowNo": false
|
|
807
|
+
},
|
|
808
|
+
"json": {
|
|
809
|
+
"name": "json",
|
|
810
|
+
"type": "boolean",
|
|
811
|
+
"description": "Print the result as JSON",
|
|
812
|
+
"allowNo": false
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
"args": {}
|
|
816
|
+
},
|
|
787
817
|
"schema:pull": {
|
|
788
818
|
"id": "schema:pull",
|
|
789
819
|
"description": "Pull remote schema to local .apsorc",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"mcpName": "io.github.apsoai/apso",
|
|
5
5
|
"description": "Apso CLI",
|
|
6
6
|
"author": "Apso by Mavric - @mavric",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"/oclif.manifest.json"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@apso/schema-tools": "^0.1.0",
|
|
27
28
|
"@biomejs/biome": "^1.9.4",
|
|
28
29
|
"@electric-sql/pglite": "^0.2.17",
|
|
29
30
|
"@modelcontextprotocol/sdk": "^1.29.0",
|