@dforge-core/dforge-mcp 0.2.18 → 0.2.22
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
|
@@ -4,6 +4,78 @@ All notable changes to `@dforge-core/dforge-mcp`. This project uses semver-ish
|
|
|
4
4
|
`0.1.0-rc.N` pre-release tags; the published version is set at publish time via
|
|
5
5
|
the release workflow, so committed `package.json` versions are placeholders.
|
|
6
6
|
|
|
7
|
+
## 0.2.19
|
|
8
|
+
|
|
9
|
+
A module could declare a dependency but not ship the contract the platform
|
|
10
|
+
requires for it, and nothing here said the contract existed. The manifest
|
|
11
|
+
reference's own minimal example carried a dependency on `admin` that made every
|
|
12
|
+
module copying it unpackable.
|
|
13
|
+
|
|
14
|
+
### Fixed — `dforge_dependency_add` writes the contract, not just the manifest
|
|
15
|
+
|
|
16
|
+
Every entry in a manifest's `dependencies` needs a matching `deps/<module>.json`
|
|
17
|
+
contract declaring what the module consumes from that dependency; without it the
|
|
18
|
+
package fails `dforge_module_validate`, `dforge_module_pack` and install alike.
|
|
19
|
+
The tool wrote only the manifest half, so the one tool meant to author this exact
|
|
20
|
+
manifest key left the module in a state it could not be packed from
|
|
21
|
+
(dForge-core#1090).
|
|
22
|
+
|
|
23
|
+
It now writes both files, with the version mirrored verbatim — the validator
|
|
24
|
+
compares the two ranges and a drift is a hard error.
|
|
25
|
+
|
|
26
|
+
`entities` is now **required**, and that is the point rather than a side effect: a
|
|
27
|
+
contract must name at least one consumed entity, and a dependency you cannot name
|
|
28
|
+
one for is a dependency you should not declare. Two new optional inputs carry the
|
|
29
|
+
detail the contract records — `use` for provenance tokens (`ref:invoice.customer`,
|
|
30
|
+
`extends:parties.party`, `role:fin_admin`) and `pks` to override a provider primary
|
|
31
|
+
key that is not `<entity>_id`. Both are guessed when omitted and the result says so.
|
|
32
|
+
|
|
33
|
+
Declaring a system module (`admin`, `metadata`, `workspace`) now warns: they are
|
|
34
|
+
provisioned into every tenant before any other module installs, so depending on one
|
|
35
|
+
buys nothing on its own — the only meaningful use is gating a minimum platform
|
|
36
|
+
version for a feature, and that still needs a contract naming the entity the feature
|
|
37
|
+
introduced. The tool also refuses to overwrite an existing contract file.
|
|
38
|
+
|
|
39
|
+
### Fixed — the manifest reference told authors to always depend on `admin`
|
|
40
|
+
|
|
41
|
+
`dforge://reference/manifest` carried **"Always depend on `admin`"** in bold, and
|
|
42
|
+
put `"admin": ">=0.0.1"` in its minimal example and its bridge-module example. That
|
|
43
|
+
advice predates dependency contracts and now produces a module that cannot be
|
|
44
|
+
packed. All three are corrected, and the reference gains a **Dependency contracts**
|
|
45
|
+
section: the file shape, the version-match rule, storage classes (`int8` for a
|
|
46
|
+
`cuid` primary key), which columns must never be declared, and the provenance kinds.
|
|
47
|
+
|
|
48
|
+
### Added — `dforge://schema/deps`
|
|
49
|
+
|
|
50
|
+
`deps.schema.json` shipped in the package but was never registered as a resource, so
|
|
51
|
+
an author who found the file name still had no way to read its shape. It is now
|
|
52
|
+
served like every other schema.
|
|
53
|
+
|
|
54
|
+
### Added — scheduled jobs resolve folder-scoped settings
|
|
55
|
+
|
|
56
|
+
`dforge://reference/jobs` and `.../settings` document the folder a cron fire runs in.
|
|
57
|
+
A scheduled action has no user, so nothing supplies a folder the way a request does;
|
|
58
|
+
the scheduler binds `scheduled_job.folder_id` when an admin has set one, and the
|
|
59
|
+
owning module's root folder otherwise. That is what makes `getSetting()` and
|
|
60
|
+
`nextNumber()` resolve inside a job. There is no `folder` key in the manifest — the
|
|
61
|
+
binding is operator state, and an upgrade never overwrites it.
|
|
62
|
+
|
|
63
|
+
### Changed — scaffolded entities ship a `name` column
|
|
64
|
+
|
|
65
|
+
`@dforge-core/dforge-cli` 0.2.17 gives the `identity` trait a visible `name`
|
|
66
|
+
column (`varchar`, `VEM`) and points the entity's `toString` at `{name}`. An
|
|
67
|
+
entity with only a PK and audit stamps had nothing renderable: its grid view
|
|
68
|
+
failed the data-view column check and `toString` had nothing to interpolate.
|
|
69
|
+
That is the upstream fix `withIdentityToString`'s `{id}` repair was standing in
|
|
70
|
+
for, so the repair no longer fires on freshly scaffolded modules.
|
|
71
|
+
|
|
72
|
+
### Added — `dforge_module_create` writes `.zed/tasks.json`
|
|
73
|
+
|
|
74
|
+
Zed has no extension command API, so the pack / install / auth dev loop ships as
|
|
75
|
+
project-local tasks. Read off the CLI templates module by feature detection rather
|
|
76
|
+
than imported: the helper landed after `@dforge-core/dforge-cli` 0.2.16, and
|
|
77
|
+
scaffolding must not break for anyone still on an older CLI.
|
|
78
|
+
|
|
7
79
|
## 0.2.18
|
|
8
80
|
|
|
9
81
|
Every counter a module updates — an on-hand quantity, a running balance, a used
|
package/dist/server.js
CHANGED
|
@@ -3226,8 +3226,8 @@ var require_utils = __commonJS({
|
|
|
3226
3226
|
}
|
|
3227
3227
|
return ind;
|
|
3228
3228
|
}
|
|
3229
|
-
function removeDotSegments(
|
|
3230
|
-
let input =
|
|
3229
|
+
function removeDotSegments(path18) {
|
|
3230
|
+
let input = path18;
|
|
3231
3231
|
const output = [];
|
|
3232
3232
|
let nextSlash = -1;
|
|
3233
3233
|
let len = 0;
|
|
@@ -3479,8 +3479,8 @@ var require_schemes = __commonJS({
|
|
|
3479
3479
|
wsComponent.secure = void 0;
|
|
3480
3480
|
}
|
|
3481
3481
|
if (wsComponent.resourceName) {
|
|
3482
|
-
const [
|
|
3483
|
-
wsComponent.path =
|
|
3482
|
+
const [path18, query] = wsComponent.resourceName.split("?");
|
|
3483
|
+
wsComponent.path = path18 && path18 !== "/" ? path18 : void 0;
|
|
3484
3484
|
wsComponent.query = query;
|
|
3485
3485
|
wsComponent.resourceName = void 0;
|
|
3486
3486
|
}
|
|
@@ -6899,12 +6899,12 @@ var require_dist = __commonJS({
|
|
|
6899
6899
|
throw new Error(`Unknown format "${name}"`);
|
|
6900
6900
|
return f;
|
|
6901
6901
|
};
|
|
6902
|
-
function addFormats(ajv, list,
|
|
6902
|
+
function addFormats(ajv, list, fs15, exportName) {
|
|
6903
6903
|
var _a3;
|
|
6904
6904
|
var _b;
|
|
6905
6905
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6906
6906
|
for (const f of list)
|
|
6907
|
-
ajv.addFormat(f,
|
|
6907
|
+
ajv.addFormat(f, fs15[f]);
|
|
6908
6908
|
}
|
|
6909
6909
|
module2.exports = exports2 = formatsPlugin;
|
|
6910
6910
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -7271,8 +7271,8 @@ function getErrorMap() {
|
|
|
7271
7271
|
|
|
7272
7272
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v3/helpers/parseUtil.js
|
|
7273
7273
|
var makeIssue = (params) => {
|
|
7274
|
-
const { data, path:
|
|
7275
|
-
const fullPath = [...
|
|
7274
|
+
const { data, path: path18, errorMaps, issueData } = params;
|
|
7275
|
+
const fullPath = [...path18, ...issueData.path || []];
|
|
7276
7276
|
const fullIssue = {
|
|
7277
7277
|
...issueData,
|
|
7278
7278
|
path: fullPath
|
|
@@ -7387,11 +7387,11 @@ var errorUtil;
|
|
|
7387
7387
|
|
|
7388
7388
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v3/types.js
|
|
7389
7389
|
var ParseInputLazyPath = class {
|
|
7390
|
-
constructor(parent, value,
|
|
7390
|
+
constructor(parent, value, path18, key) {
|
|
7391
7391
|
this._cachedPath = [];
|
|
7392
7392
|
this.parent = parent;
|
|
7393
7393
|
this.data = value;
|
|
7394
|
-
this._path =
|
|
7394
|
+
this._path = path18;
|
|
7395
7395
|
this._key = key;
|
|
7396
7396
|
}
|
|
7397
7397
|
get path() {
|
|
@@ -11311,10 +11311,10 @@ function mergeDefs(...defs) {
|
|
|
11311
11311
|
function cloneDef(schema2) {
|
|
11312
11312
|
return mergeDefs(schema2._zod.def);
|
|
11313
11313
|
}
|
|
11314
|
-
function getElementAtPath(obj,
|
|
11315
|
-
if (!
|
|
11314
|
+
function getElementAtPath(obj, path18) {
|
|
11315
|
+
if (!path18)
|
|
11316
11316
|
return obj;
|
|
11317
|
-
return
|
|
11317
|
+
return path18.reduce((acc, key) => acc?.[key], obj);
|
|
11318
11318
|
}
|
|
11319
11319
|
function promiseAllObject(promisesObj) {
|
|
11320
11320
|
const keys = Object.keys(promisesObj);
|
|
@@ -11723,11 +11723,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
11723
11723
|
}
|
|
11724
11724
|
return false;
|
|
11725
11725
|
}
|
|
11726
|
-
function prefixIssues(
|
|
11726
|
+
function prefixIssues(path18, issues) {
|
|
11727
11727
|
return issues.map((iss) => {
|
|
11728
11728
|
var _a3;
|
|
11729
11729
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
11730
|
-
iss.path.unshift(
|
|
11730
|
+
iss.path.unshift(path18);
|
|
11731
11731
|
return iss;
|
|
11732
11732
|
});
|
|
11733
11733
|
}
|
|
@@ -11874,16 +11874,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
11874
11874
|
}
|
|
11875
11875
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
11876
11876
|
const fieldErrors = { _errors: [] };
|
|
11877
|
-
const processError = (error52,
|
|
11877
|
+
const processError = (error52, path18 = []) => {
|
|
11878
11878
|
for (const issue2 of error52.issues) {
|
|
11879
11879
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
11880
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
11880
|
+
issue2.errors.map((issues) => processError({ issues }, [...path18, ...issue2.path]));
|
|
11881
11881
|
} else if (issue2.code === "invalid_key") {
|
|
11882
|
-
processError({ issues: issue2.issues }, [...
|
|
11882
|
+
processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
|
|
11883
11883
|
} else if (issue2.code === "invalid_element") {
|
|
11884
|
-
processError({ issues: issue2.issues }, [...
|
|
11884
|
+
processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
|
|
11885
11885
|
} else {
|
|
11886
|
-
const fullpath = [...
|
|
11886
|
+
const fullpath = [...path18, ...issue2.path];
|
|
11887
11887
|
if (fullpath.length === 0) {
|
|
11888
11888
|
fieldErrors._errors.push(mapper(issue2));
|
|
11889
11889
|
} else {
|
|
@@ -11910,17 +11910,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
11910
11910
|
}
|
|
11911
11911
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
11912
11912
|
const result = { errors: [] };
|
|
11913
|
-
const processError = (error52,
|
|
11913
|
+
const processError = (error52, path18 = []) => {
|
|
11914
11914
|
var _a3, _b;
|
|
11915
11915
|
for (const issue2 of error52.issues) {
|
|
11916
11916
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
11917
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
11917
|
+
issue2.errors.map((issues) => processError({ issues }, [...path18, ...issue2.path]));
|
|
11918
11918
|
} else if (issue2.code === "invalid_key") {
|
|
11919
|
-
processError({ issues: issue2.issues }, [...
|
|
11919
|
+
processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
|
|
11920
11920
|
} else if (issue2.code === "invalid_element") {
|
|
11921
|
-
processError({ issues: issue2.issues }, [...
|
|
11921
|
+
processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
|
|
11922
11922
|
} else {
|
|
11923
|
-
const fullpath = [...
|
|
11923
|
+
const fullpath = [...path18, ...issue2.path];
|
|
11924
11924
|
if (fullpath.length === 0) {
|
|
11925
11925
|
result.errors.push(mapper(issue2));
|
|
11926
11926
|
continue;
|
|
@@ -11952,8 +11952,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
11952
11952
|
}
|
|
11953
11953
|
function toDotPath(_path) {
|
|
11954
11954
|
const segs = [];
|
|
11955
|
-
const
|
|
11956
|
-
for (const seg of
|
|
11955
|
+
const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
11956
|
+
for (const seg of path18) {
|
|
11957
11957
|
if (typeof seg === "number")
|
|
11958
11958
|
segs.push(`[${seg}]`);
|
|
11959
11959
|
else if (typeof seg === "symbol")
|
|
@@ -23065,11 +23065,11 @@ function normalizeObjectSchema(schema2) {
|
|
|
23065
23065
|
}
|
|
23066
23066
|
return void 0;
|
|
23067
23067
|
}
|
|
23068
|
-
function getDotPath(
|
|
23069
|
-
if (
|
|
23068
|
+
function getDotPath(path18) {
|
|
23069
|
+
if (path18.length === 0) {
|
|
23070
23070
|
return "object root";
|
|
23071
23071
|
}
|
|
23072
|
-
return
|
|
23072
|
+
return path18.reduce((acc, seg, index) => {
|
|
23073
23073
|
if (index === 0) {
|
|
23074
23074
|
return String(seg);
|
|
23075
23075
|
}
|
|
@@ -25094,13 +25094,13 @@ function resolveRef(ref, ctx) {
|
|
|
25094
25094
|
if (!ref.startsWith("#")) {
|
|
25095
25095
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
25096
25096
|
}
|
|
25097
|
-
const
|
|
25098
|
-
if (
|
|
25097
|
+
const path18 = ref.slice(1).split("/").filter(Boolean);
|
|
25098
|
+
if (path18.length === 0) {
|
|
25099
25099
|
return ctx.rootSchema;
|
|
25100
25100
|
}
|
|
25101
25101
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
25102
|
-
if (
|
|
25103
|
-
const key =
|
|
25102
|
+
if (path18[0] === defsKey) {
|
|
25103
|
+
const key = path18[1];
|
|
25104
25104
|
if (!key || !ctx.defs[key]) {
|
|
25105
25105
|
throw new Error(`Reference not found: ${ref}`);
|
|
25106
25106
|
}
|
|
@@ -31002,7 +31002,7 @@ var import_node_crypto = require("crypto");
|
|
|
31002
31002
|
var fs2 = __toESM(require("fs"));
|
|
31003
31003
|
var path2 = __toESM(require("path"));
|
|
31004
31004
|
|
|
31005
|
-
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.
|
|
31005
|
+
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.17/node_modules/@dforge-core/dforge-cli/dist/lib.mjs
|
|
31006
31006
|
function buildManifest(opts, moduleId) {
|
|
31007
31007
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31008
31008
|
const manifest = {
|
|
@@ -31023,19 +31023,36 @@ function buildManifest(opts, moduleId) {
|
|
|
31023
31023
|
if (opts.license) manifest.license = opts.license;
|
|
31024
31024
|
if (opts.dependencies.length > 0) {
|
|
31025
31025
|
manifest.dependencies = Object.fromEntries(
|
|
31026
|
-
opts.dependencies.map(
|
|
31026
|
+
opts.dependencies.map(
|
|
31027
|
+
(d) => typeof d === "string" ? [d, DEPENDENCY_RANGE] : [d.module, { version: DEPENDENCY_RANGE, entities: d.entities }]
|
|
31028
|
+
)
|
|
31027
31029
|
);
|
|
31028
31030
|
}
|
|
31029
31031
|
return manifest;
|
|
31030
31032
|
}
|
|
31033
|
+
var DEPENDENCY_RANGE = ">=0.0.1";
|
|
31034
|
+
var NAME_FIELD = "name";
|
|
31031
31035
|
function buildEntity(entity) {
|
|
31032
31036
|
const traits2 = entity.traits === "identity+audit" ? ["identity", "audit"] : ["identity"];
|
|
31033
31037
|
const built = {
|
|
31034
31038
|
description: entity.label,
|
|
31035
31039
|
dbObject: entity.name,
|
|
31036
|
-
|
|
31040
|
+
// The traits only add a PK and audit stamps, so an entity with no fields
|
|
31041
|
+
// of its own has nothing renderable: its grid view fails the data-view
|
|
31042
|
+
// column check and `toString` has nothing to interpolate. Scaffold one
|
|
31043
|
+
// visible name column so the module validates and the grid shows a row.
|
|
31044
|
+
toString: `{${NAME_FIELD}}`,
|
|
31037
31045
|
traits: traits2,
|
|
31038
|
-
fields: {
|
|
31046
|
+
fields: {
|
|
31047
|
+
[NAME_FIELD]: {
|
|
31048
|
+
dbDatatype: "varchar",
|
|
31049
|
+
fieldTypeCd: "text",
|
|
31050
|
+
flags: "VEM",
|
|
31051
|
+
maxLen: 200,
|
|
31052
|
+
orderNum: 10,
|
|
31053
|
+
description: "Name"
|
|
31054
|
+
}
|
|
31055
|
+
}
|
|
31039
31056
|
};
|
|
31040
31057
|
if (entity.constraints?.length) {
|
|
31041
31058
|
built.constraints = Object.fromEntries(
|
|
@@ -31120,7 +31137,8 @@ function buildTranslations(opts) {
|
|
|
31120
31137
|
for (const e of opts.entities) {
|
|
31121
31138
|
const fields = {
|
|
31122
31139
|
// identity trait → primary key column
|
|
31123
|
-
[`${e.name}_id`]: { label: `${e.label} ID` }
|
|
31140
|
+
[`${e.name}_id`]: { label: `${e.label} ID` },
|
|
31141
|
+
[NAME_FIELD]: { label: "Name" }
|
|
31124
31142
|
};
|
|
31125
31143
|
if (e.traits === "identity+audit") {
|
|
31126
31144
|
fields.created_date = { label: "Created Date" };
|
|
@@ -31161,8 +31179,9 @@ function buildTranslations(opts) {
|
|
|
31161
31179
|
}
|
|
31162
31180
|
};
|
|
31163
31181
|
}
|
|
31164
|
-
function buildSeedData() {
|
|
31165
|
-
|
|
31182
|
+
function buildSeedData(entity) {
|
|
31183
|
+
const entityCode = typeof entity === "string" ? entity : entity?.name ?? "";
|
|
31184
|
+
return { entityCode, records: [] };
|
|
31166
31185
|
}
|
|
31167
31186
|
function buildGitignore() {
|
|
31168
31187
|
return [
|
|
@@ -31184,30 +31203,80 @@ var SCHEMA_BINDINGS = [
|
|
|
31184
31203
|
{ fileMatch: ["ui/folders.json"], schema: "folders" },
|
|
31185
31204
|
{ fileMatch: ["ui/menus.json"], schema: "menus" },
|
|
31186
31205
|
{ fileMatch: ["ui/reports.json"], schema: "reports" },
|
|
31206
|
+
{ fileMatch: ["ui/print_templates.json"], schema: "print-templates" },
|
|
31187
31207
|
{ fileMatch: ["security/roles.json"], schema: "roles" },
|
|
31188
31208
|
{ fileMatch: ["logic/jobs.json"], schema: "jobs" },
|
|
31209
|
+
{ fileMatch: ["logic/triggers.json"], schema: "triggers" },
|
|
31210
|
+
{ fileMatch: ["logic/webhooks.json"], schema: "webhooks" },
|
|
31211
|
+
{ fileMatch: ["deps/*.json"], schema: "deps" },
|
|
31189
31212
|
{ fileMatch: ["seed-data/*.json"], schema: "seed-data" },
|
|
31190
31213
|
{ fileMatch: ["settings.json"], schema: "settings" }
|
|
31191
31214
|
];
|
|
31215
|
+
function schemaEntries() {
|
|
31216
|
+
return SCHEMA_BINDINGS.map((b) => ({
|
|
31217
|
+
fileMatch: b.fileMatch,
|
|
31218
|
+
url: `${SCHEMA_BASE}/${b.schema}.schema.json`
|
|
31219
|
+
}));
|
|
31220
|
+
}
|
|
31192
31221
|
function buildVscodeSettings() {
|
|
31193
|
-
return {
|
|
31194
|
-
"json.schemas": SCHEMA_BINDINGS.map((b) => ({
|
|
31195
|
-
fileMatch: b.fileMatch,
|
|
31196
|
-
url: `${SCHEMA_BASE}/${b.schema}.schema.json`
|
|
31197
|
-
}))
|
|
31198
|
-
};
|
|
31222
|
+
return { "json.schemas": schemaEntries() };
|
|
31199
31223
|
}
|
|
31200
31224
|
function buildZedSettings() {
|
|
31201
31225
|
return {
|
|
31202
|
-
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
fileMatch: b.fileMatch,
|
|
31206
|
-
url: `${SCHEMA_BASE}/${b.schema}.schema.json`
|
|
31207
|
-
}))
|
|
31226
|
+
lsp: {
|
|
31227
|
+
"json-language-server": {
|
|
31228
|
+
settings: { json: { schemas: schemaEntries() } }
|
|
31208
31229
|
}
|
|
31209
|
-
}
|
|
31210
|
-
|
|
31230
|
+
},
|
|
31231
|
+
context_servers: {
|
|
31232
|
+
dforge: {
|
|
31233
|
+
source: "custom",
|
|
31234
|
+
command: "npx",
|
|
31235
|
+
args: ["-y", "@dforge-core/dforge-mcp@latest"],
|
|
31236
|
+
env: {}
|
|
31237
|
+
}
|
|
31238
|
+
}
|
|
31239
|
+
};
|
|
31240
|
+
}
|
|
31241
|
+
function buildZedTasks() {
|
|
31242
|
+
const CLI = "npx";
|
|
31243
|
+
const CLI_ARGS = ["-y", "@dforge-core/dforge-cli@latest"];
|
|
31244
|
+
const DEFAULT_TENANT = "http://localhost:5179";
|
|
31245
|
+
const task = (label, args) => ({
|
|
31246
|
+
label: `dForge: ${label}`,
|
|
31247
|
+
command: CLI,
|
|
31248
|
+
args: [...CLI_ARGS, ...args],
|
|
31249
|
+
cwd: "$ZED_WORKTREE_ROOT",
|
|
31250
|
+
env: { DFORGE_URL: DEFAULT_TENANT },
|
|
31251
|
+
use_new_terminal: false,
|
|
31252
|
+
allow_concurrent_runs: false,
|
|
31253
|
+
reveal: "always"
|
|
31254
|
+
});
|
|
31255
|
+
return [
|
|
31256
|
+
task("Pack module", ["module", "pack", "$ZED_WORKTREE_ROOT"]),
|
|
31257
|
+
// --force matches the extension's dev-loop default: without it the CLI
|
|
31258
|
+
// skips the reinstall when the version hasn't changed and your edits
|
|
31259
|
+
// silently never reach the tenant.
|
|
31260
|
+
task("Install module to tenant", [
|
|
31261
|
+
"module",
|
|
31262
|
+
"install",
|
|
31263
|
+
"--path",
|
|
31264
|
+
"$ZED_WORKTREE_ROOT",
|
|
31265
|
+
"--url",
|
|
31266
|
+
"$DFORGE_URL",
|
|
31267
|
+
"--force"
|
|
31268
|
+
]),
|
|
31269
|
+
task("Sign in to tenant", [
|
|
31270
|
+
"auth",
|
|
31271
|
+
"login",
|
|
31272
|
+
"--url",
|
|
31273
|
+
"$DFORGE_URL",
|
|
31274
|
+
"--login-url",
|
|
31275
|
+
"$DFORGE_URL"
|
|
31276
|
+
]),
|
|
31277
|
+
task("Show signed-in user", ["auth", "whoami", "--url", "$DFORGE_URL"]),
|
|
31278
|
+
task("List installed modules", ["module", "list", "--url", "$DFORGE_URL"])
|
|
31279
|
+
];
|
|
31211
31280
|
}
|
|
31212
31281
|
|
|
31213
31282
|
// src/tools/_helpers.ts
|
|
@@ -31759,6 +31828,8 @@ function createModuleFiles(args) {
|
|
|
31759
31828
|
writeText(".gitignore", buildGitignore());
|
|
31760
31829
|
write(".vscode/settings.json", buildVscodeSettings());
|
|
31761
31830
|
write(".zed/settings.json", buildZedSettings());
|
|
31831
|
+
const buildZedTasks2 = buildZedTasks;
|
|
31832
|
+
if (buildZedTasks2) write(".zed/tasks.json", buildZedTasks2());
|
|
31762
31833
|
if (opts.preset === "full") {
|
|
31763
31834
|
write("settings.json", buildSettings());
|
|
31764
31835
|
write("translations/en-US.json", buildTranslations(opts));
|
|
@@ -35182,6 +35253,8 @@ ${warnings.map((w) => ` \u2022 ${w}`).join("\n")}` : void 0
|
|
|
35182
35253
|
}
|
|
35183
35254
|
|
|
35184
35255
|
// src/tools/adds.ts
|
|
35256
|
+
var fs10 = __toESM(require("fs"));
|
|
35257
|
+
var path13 = __toESM(require("path"));
|
|
35185
35258
|
var reportAddSchema = {
|
|
35186
35259
|
moduleDir: external_exports.string(),
|
|
35187
35260
|
code: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("Report code (keys ui/reports.json)."),
|
|
@@ -35326,16 +35399,23 @@ function folderAdd(args) {
|
|
|
35326
35399
|
}
|
|
35327
35400
|
);
|
|
35328
35401
|
}
|
|
35402
|
+
var SYSTEM_MODULES = /* @__PURE__ */ new Set(["admin", "metadata", "workspace"]);
|
|
35329
35403
|
var dependencyAddSchema = {
|
|
35330
35404
|
moduleDir: external_exports.string(),
|
|
35331
35405
|
moduleCode: external_exports.string().regex(/^[a-z][a-z0-9_-]*$/).describe("Module to depend on."),
|
|
35332
35406
|
version: external_exports.string().default(">=0.1.0").describe("Semver range."),
|
|
35333
|
-
entities: external_exports.array(external_exports.string()).
|
|
35334
|
-
"
|
|
35407
|
+
entities: external_exports.array(external_exports.string()).min(1).describe(
|
|
35408
|
+
"The provider entities this module consumes, e.g. ['party']. Required: every dependency needs a deps/<module>.json contract naming at least one entity, and a dependency you cannot name a consumed entity for should not be declared at all. Also narrows the manifest dep to the object form `{ version, entities }`."
|
|
35409
|
+
),
|
|
35410
|
+
use: external_exports.array(external_exports.string()).optional().describe(
|
|
35411
|
+
"Provenance tokens recorded against each declared entity: '<kind>:<symbol>' where kind is ref|extends|formula|action|view|report|print|role, e.g. 'ref:invoice.customer'. 'extends' must be fully qualified ('extends:parties.party'). Defaults to a placeholder you must refine."
|
|
35412
|
+
),
|
|
35413
|
+
pks: external_exports.record(external_exports.string(), external_exports.string()).optional().describe(
|
|
35414
|
+
`Override the provider PK column per entity, e.g. { "party": "party_uid" }. Defaults to '<entity>_id', which is what the 'identity' trait produces.`
|
|
35335
35415
|
)
|
|
35336
35416
|
};
|
|
35337
35417
|
function dependencyAdd(args) {
|
|
35338
|
-
const { manifest } = loadManifest(args.moduleDir);
|
|
35418
|
+
const { manifest, paths } = loadManifest(args.moduleDir);
|
|
35339
35419
|
if (manifest.code === args.moduleCode) {
|
|
35340
35420
|
throw new Error("A module can't depend on itself.");
|
|
35341
35421
|
}
|
|
@@ -35345,14 +35425,57 @@ function dependencyAdd(args) {
|
|
|
35345
35425
|
`Dependency on '${args.moduleCode}' already exists. Edit manifest.json directly to change the version.`
|
|
35346
35426
|
);
|
|
35347
35427
|
}
|
|
35348
|
-
const
|
|
35428
|
+
const contractPath = path13.join(paths.root, "deps", `${args.moduleCode}.json`);
|
|
35429
|
+
if (fs10.existsSync(contractPath)) {
|
|
35430
|
+
throw new Error(
|
|
35431
|
+
`deps/${args.moduleCode}.json already exists but '${args.moduleCode}' is not a manifest dependency. Add the manifest entry by hand or delete the stale contract \u2014 this tool will not overwrite it.`
|
|
35432
|
+
);
|
|
35433
|
+
}
|
|
35349
35434
|
const newManifest = withTodayStamp({
|
|
35350
35435
|
...manifest,
|
|
35351
|
-
dependencies: {
|
|
35436
|
+
dependencies: {
|
|
35437
|
+
...deps,
|
|
35438
|
+
[args.moduleCode]: { version: args.version, entities: args.entities }
|
|
35439
|
+
}
|
|
35352
35440
|
});
|
|
35441
|
+
const placeholderUse = !args.use || args.use.length === 0;
|
|
35442
|
+
const use = placeholderUse ? [`ref:${manifest.code}`] : args.use;
|
|
35443
|
+
const contract = {
|
|
35444
|
+
module: args.moduleCode,
|
|
35445
|
+
version: args.version,
|
|
35446
|
+
entities: Object.fromEntries(
|
|
35447
|
+
args.entities.map((e) => [
|
|
35448
|
+
e,
|
|
35449
|
+
{ pk: args.pks?.[e] ?? `${e}_id`, use: [...use] }
|
|
35450
|
+
])
|
|
35451
|
+
)
|
|
35452
|
+
};
|
|
35453
|
+
const warnings = [];
|
|
35454
|
+
if (placeholderUse) {
|
|
35455
|
+
warnings.push(
|
|
35456
|
+
`deps/${args.moduleCode}.json was written with a placeholder provenance token 'ref:${manifest.code}'. Replace it with the real usage (e.g. 'ref:<my_entity>.<my_column>', 'extends:${args.moduleCode}.<entity>', 'role:<role_cd>') \u2014 it is the record of WHY the dependency exists.`
|
|
35457
|
+
);
|
|
35458
|
+
}
|
|
35459
|
+
if (!args.pks) {
|
|
35460
|
+
warnings.push(
|
|
35461
|
+
`PKs were guessed as '<entity>_id'. Check them against ${args.moduleCode}'s entity files \u2014 a wrong pk fails the install-time satisfaction check.`
|
|
35462
|
+
);
|
|
35463
|
+
}
|
|
35464
|
+
if (SYSTEM_MODULES.has(args.moduleCode)) {
|
|
35465
|
+
warnings.push(
|
|
35466
|
+
`'${args.moduleCode}' is a system module \u2014 always provisioned, so this dependency is only meaningful as a minimum-platform-version gate. If you are not gating a platform feature, drop it.`
|
|
35467
|
+
);
|
|
35468
|
+
}
|
|
35469
|
+
warnings.push(
|
|
35470
|
+
`Declare the columns you actually read, write or join on under each entity's 'columns' \u2014 see dforge://schema/deps.`
|
|
35471
|
+
);
|
|
35353
35472
|
return makeResult(
|
|
35354
|
-
`Added dependency on '${args.moduleCode}' (${args.version})
|
|
35355
|
-
{
|
|
35473
|
+
`Added dependency on '${args.moduleCode}' (${args.version}) for entities [${args.entities.join(", ")}], with the required deps/${args.moduleCode}.json contract.`,
|
|
35474
|
+
{
|
|
35475
|
+
"manifest.json": jsonText(newManifest),
|
|
35476
|
+
[`deps/${args.moduleCode}.json`]: jsonText(contract)
|
|
35477
|
+
},
|
|
35478
|
+
warnings.join("\n")
|
|
35356
35479
|
);
|
|
35357
35480
|
}
|
|
35358
35481
|
|
|
@@ -35396,8 +35519,8 @@ function roleRightSet(args) {
|
|
|
35396
35519
|
}
|
|
35397
35520
|
|
|
35398
35521
|
// src/tools/module-inspect.ts
|
|
35399
|
-
var
|
|
35400
|
-
var
|
|
35522
|
+
var fs11 = __toESM(require("fs"));
|
|
35523
|
+
var path14 = __toESM(require("path"));
|
|
35401
35524
|
var moduleInspectSchema = {
|
|
35402
35525
|
moduleDir: external_exports.string()
|
|
35403
35526
|
};
|
|
@@ -35405,7 +35528,7 @@ function moduleInspect(args) {
|
|
|
35405
35528
|
const { paths, manifest } = loadManifest(args.moduleDir);
|
|
35406
35529
|
const entities = manifest.entities ?? {};
|
|
35407
35530
|
const entitySummaries = Object.entries(entities).map(([name, relPath]) => {
|
|
35408
|
-
const abs =
|
|
35531
|
+
const abs = path14.join(paths.root, relPath.replace(/^\.\//, ""));
|
|
35409
35532
|
const e = readJsonOrDefault(abs, {});
|
|
35410
35533
|
const fields = e.fields ?? {};
|
|
35411
35534
|
return {
|
|
@@ -35489,8 +35612,8 @@ function moduleInspect(args) {
|
|
|
35489
35612
|
readJsonOrDefault(paths.printTemplates, {})
|
|
35490
35613
|
);
|
|
35491
35614
|
const domains = Object.keys(readJsonOrDefault(paths.domains, {}));
|
|
35492
|
-
const seedFiles =
|
|
35493
|
-
const translations =
|
|
35615
|
+
const seedFiles = fs11.existsSync(paths.seedDataDir) ? fs11.readdirSync(paths.seedDataDir).filter((f) => f.endsWith(".json")).sort() : [];
|
|
35616
|
+
const translations = fs11.existsSync(paths.translationsDir) ? fs11.readdirSync(paths.translationsDir).filter((f) => f.endsWith(".json")).sort() : [];
|
|
35494
35617
|
const summary = {
|
|
35495
35618
|
module: {
|
|
35496
35619
|
code: manifest.code,
|
|
@@ -35534,8 +35657,8 @@ function computeDepth(folder, current = 1) {
|
|
|
35534
35657
|
}
|
|
35535
35658
|
|
|
35536
35659
|
// src/tools/module-validate.ts
|
|
35537
|
-
var
|
|
35538
|
-
var
|
|
35660
|
+
var fs12 = __toESM(require("fs"));
|
|
35661
|
+
var path15 = __toESM(require("path"));
|
|
35539
35662
|
var moduleValidateSchema = {
|
|
35540
35663
|
moduleDir: external_exports.string().describe("Path to the module root. Run this after authoring and before dforge_module_pack.")
|
|
35541
35664
|
};
|
|
@@ -35555,12 +35678,12 @@ function hasVisibleScalarColumn(fields) {
|
|
|
35555
35678
|
return false;
|
|
35556
35679
|
}
|
|
35557
35680
|
function resolveTranslationFile(translationsDir, locale) {
|
|
35558
|
-
const exact =
|
|
35559
|
-
if (
|
|
35560
|
-
if (!
|
|
35681
|
+
const exact = path15.join(translationsDir, `${locale}.json`);
|
|
35682
|
+
if (fs12.existsSync(exact)) return exact;
|
|
35683
|
+
if (!fs12.existsSync(translationsDir)) return void 0;
|
|
35561
35684
|
const want = `${locale}.json`.toLowerCase();
|
|
35562
|
-
for (const f of
|
|
35563
|
-
if (f.toLowerCase() === want) return
|
|
35685
|
+
for (const f of fs12.readdirSync(translationsDir)) {
|
|
35686
|
+
if (f.toLowerCase() === want) return path15.join(translationsDir, f);
|
|
35564
35687
|
}
|
|
35565
35688
|
return void 0;
|
|
35566
35689
|
}
|
|
@@ -35588,14 +35711,14 @@ function moduleValidate(args) {
|
|
|
35588
35711
|
const fieldDefsOf = {};
|
|
35589
35712
|
for (const [name, relPath] of Object.entries(entityMap)) {
|
|
35590
35713
|
if (name.includes(".")) continue;
|
|
35591
|
-
const abs =
|
|
35592
|
-
if (!
|
|
35714
|
+
const abs = path15.join(paths.root, relPath.replace(/^\.\//, ""));
|
|
35715
|
+
if (!fs12.existsSync(abs)) {
|
|
35593
35716
|
err(`manifest.entities.${name}`, `points to '${relPath}' which does not exist on disk`);
|
|
35594
35717
|
continue;
|
|
35595
35718
|
}
|
|
35596
35719
|
let e;
|
|
35597
35720
|
try {
|
|
35598
|
-
e = JSON.parse(
|
|
35721
|
+
e = JSON.parse(fs12.readFileSync(abs, "utf8"));
|
|
35599
35722
|
} catch (ex) {
|
|
35600
35723
|
err(`entities/${name}.json`, `invalid JSON: ${ex.message}`);
|
|
35601
35724
|
continue;
|
|
@@ -35831,7 +35954,7 @@ function moduleValidate(args) {
|
|
|
35831
35954
|
const abs = resolveTranslationFile(paths.translationsDir, locale);
|
|
35832
35955
|
if (abs) {
|
|
35833
35956
|
try {
|
|
35834
|
-
tx = JSON.parse(
|
|
35957
|
+
tx = JSON.parse(fs12.readFileSync(abs, "utf8"));
|
|
35835
35958
|
} catch {
|
|
35836
35959
|
tx = null;
|
|
35837
35960
|
}
|
|
@@ -35947,7 +36070,7 @@ function moduleValidate(args) {
|
|
|
35947
36070
|
where,
|
|
35948
36071
|
`script '${script}' must be a BARE filename \u2014 no path, no '.dsl' extension (e.g. "script": "${script.replace(/\.dsl$/, "").split(/[\\/]/).pop()}").`
|
|
35949
36072
|
);
|
|
35950
|
-
} else if (!
|
|
36073
|
+
} else if (!fs12.existsSync(path15.join(paths.logicDir, "actions", `${script}.dsl`))) {
|
|
35951
36074
|
err(where, `script '${script}' has no file at logic/actions/${script}.dsl.`);
|
|
35952
36075
|
}
|
|
35953
36076
|
const ent = act.entityCode ?? act.entity;
|
|
@@ -35987,11 +36110,11 @@ function moduleValidate(args) {
|
|
|
35987
36110
|
if (act && act in actions) {
|
|
35988
36111
|
const script = actions[act]?.script;
|
|
35989
36112
|
if (typeof script === "string") {
|
|
35990
|
-
const dslPath =
|
|
35991
|
-
if (
|
|
36113
|
+
const dslPath = path15.join(paths.logicDir, "actions", `${script}.dsl`);
|
|
36114
|
+
if (fs12.existsSync(dslPath)) {
|
|
35992
36115
|
let body = "";
|
|
35993
36116
|
try {
|
|
35994
|
-
body =
|
|
36117
|
+
body = fs12.readFileSync(dslPath, "utf8");
|
|
35995
36118
|
} catch {
|
|
35996
36119
|
}
|
|
35997
36120
|
for (const issue2 of checkDsl(body, { viaJob: true })) {
|
|
@@ -36015,11 +36138,11 @@ function moduleValidate(args) {
|
|
|
36015
36138
|
for (const [acode, a] of Object.entries(actions)) {
|
|
36016
36139
|
const script = a?.script;
|
|
36017
36140
|
if (typeof script !== "string" || !script) continue;
|
|
36018
|
-
const dslPath =
|
|
36019
|
-
if (!
|
|
36141
|
+
const dslPath = path15.join(paths.logicDir, "actions", `${script}.dsl`);
|
|
36142
|
+
if (!fs12.existsSync(dslPath)) continue;
|
|
36020
36143
|
let body;
|
|
36021
36144
|
try {
|
|
36022
|
-
body =
|
|
36145
|
+
body = fs12.readFileSync(dslPath, "utf8");
|
|
36023
36146
|
} catch {
|
|
36024
36147
|
continue;
|
|
36025
36148
|
}
|
|
@@ -36177,7 +36300,7 @@ function moduleValidate(args) {
|
|
|
36177
36300
|
}
|
|
36178
36301
|
}
|
|
36179
36302
|
const roleCodes = Object.keys(roles);
|
|
36180
|
-
const localeFiles =
|
|
36303
|
+
const localeFiles = fs12.existsSync(paths.translationsDir) ? fs12.readdirSync(paths.translationsDir).filter((f) => f.toLowerCase().endsWith(".json")).sort() : [];
|
|
36181
36304
|
for (const raw of supportedLocales) {
|
|
36182
36305
|
const locale = raw.trim();
|
|
36183
36306
|
if (!locale) continue;
|
|
@@ -36197,7 +36320,7 @@ function moduleValidate(args) {
|
|
|
36197
36320
|
for (const file2 of localeFiles) {
|
|
36198
36321
|
let tx;
|
|
36199
36322
|
try {
|
|
36200
|
-
tx = JSON.parse(
|
|
36323
|
+
tx = JSON.parse(fs12.readFileSync(path15.join(paths.translationsDir, file2), "utf8"));
|
|
36201
36324
|
} catch (ex) {
|
|
36202
36325
|
err(`translations/${file2}`, `invalid JSON: ${ex.message}`);
|
|
36203
36326
|
continue;
|
|
@@ -36382,18 +36505,18 @@ function webhookAdd(args) {
|
|
|
36382
36505
|
}
|
|
36383
36506
|
|
|
36384
36507
|
// src/tools/apply.ts
|
|
36385
|
-
var
|
|
36386
|
-
var
|
|
36508
|
+
var fs13 = __toESM(require("fs"));
|
|
36509
|
+
var path16 = __toESM(require("path"));
|
|
36387
36510
|
var applyInput = {
|
|
36388
36511
|
apply: external_exports.boolean().default(false).describe(
|
|
36389
36512
|
"When true, WRITE the resulting files to disk (and remove any `deletes`) instead of returning their contents for the client to write. The response then lists the paths touched. Use it for routine patches the user isn't reviewing line-by-line; leave it false when you want to preview a diff first."
|
|
36390
36513
|
)
|
|
36391
36514
|
};
|
|
36392
|
-
var isReportPayload = (relPath) =>
|
|
36515
|
+
var isReportPayload = (relPath) => path16.basename(relPath).startsWith("_");
|
|
36393
36516
|
function resolveInside(root, relPath) {
|
|
36394
|
-
const abs =
|
|
36395
|
-
const rel2 =
|
|
36396
|
-
if (rel2.startsWith("..") ||
|
|
36517
|
+
const abs = path16.resolve(root, relPath);
|
|
36518
|
+
const rel2 = path16.relative(root, abs);
|
|
36519
|
+
if (rel2.startsWith("..") || path16.isAbsolute(rel2)) {
|
|
36397
36520
|
throw new Error(
|
|
36398
36521
|
`Refusing to write '${relPath}' \u2014 it resolves outside the module directory (${abs}).`
|
|
36399
36522
|
);
|
|
@@ -36401,7 +36524,7 @@ function resolveInside(root, relPath) {
|
|
|
36401
36524
|
return abs;
|
|
36402
36525
|
}
|
|
36403
36526
|
function applyToDisk(moduleDir, result) {
|
|
36404
|
-
const root =
|
|
36527
|
+
const root = path16.resolve(moduleDir);
|
|
36405
36528
|
const written = [];
|
|
36406
36529
|
const skipped = [];
|
|
36407
36530
|
for (const [relPath, contents] of Object.entries(result.files ?? {})) {
|
|
@@ -36410,8 +36533,8 @@ function applyToDisk(moduleDir, result) {
|
|
|
36410
36533
|
continue;
|
|
36411
36534
|
}
|
|
36412
36535
|
const abs = resolveInside(root, relPath);
|
|
36413
|
-
|
|
36414
|
-
|
|
36536
|
+
fs13.mkdirSync(path16.dirname(abs), { recursive: true });
|
|
36537
|
+
fs13.writeFileSync(abs, contents, "utf8");
|
|
36415
36538
|
written.push(relPath);
|
|
36416
36539
|
}
|
|
36417
36540
|
const deleted = [];
|
|
@@ -36419,7 +36542,7 @@ function applyToDisk(moduleDir, result) {
|
|
|
36419
36542
|
const abs = resolveInside(root, relPath);
|
|
36420
36543
|
let stat;
|
|
36421
36544
|
try {
|
|
36422
|
-
stat =
|
|
36545
|
+
stat = fs13.lstatSync(abs);
|
|
36423
36546
|
} catch {
|
|
36424
36547
|
continue;
|
|
36425
36548
|
}
|
|
@@ -36428,7 +36551,7 @@ function applyToDisk(moduleDir, result) {
|
|
|
36428
36551
|
`Refusing to delete '${relPath}': it is a directory, and \`deletes\` is a file-only contract (it exists to remove a renamed entity's old JSON or an orphaned seed file). Remove the directory by hand if that is really what you want.`
|
|
36429
36552
|
);
|
|
36430
36553
|
}
|
|
36431
|
-
|
|
36554
|
+
fs13.rmSync(abs, { force: true });
|
|
36432
36555
|
deleted.push(relPath);
|
|
36433
36556
|
}
|
|
36434
36557
|
const out = {
|
|
@@ -36443,10 +36566,10 @@ function applyToDisk(moduleDir, result) {
|
|
|
36443
36566
|
}
|
|
36444
36567
|
|
|
36445
36568
|
// src/resources/index.ts
|
|
36446
|
-
var
|
|
36447
|
-
var
|
|
36448
|
-
var RESOURCES_DIR =
|
|
36449
|
-
var SKILL_DIR =
|
|
36569
|
+
var fs14 = __toESM(require("fs"));
|
|
36570
|
+
var path17 = __toESM(require("path"));
|
|
36571
|
+
var RESOURCES_DIR = path17.resolve(__dirname, "..", "resources");
|
|
36572
|
+
var SKILL_DIR = path17.resolve(__dirname, "..", "skills", "dforge-mcp-author");
|
|
36450
36573
|
function schema(name, label, description) {
|
|
36451
36574
|
return {
|
|
36452
36575
|
uri: `dforge://schema/${name}`,
|
|
@@ -36498,6 +36621,11 @@ var resources = [
|
|
|
36498
36621
|
"Module manifest JSON schema",
|
|
36499
36622
|
"JSON Schema for manifest.json. Consult before emitting manifest content \u2014 covers required fields, semver patterns, and the entities map."
|
|
36500
36623
|
),
|
|
36624
|
+
schema(
|
|
36625
|
+
"deps",
|
|
36626
|
+
"Dependency contract JSON schema",
|
|
36627
|
+
"JSON Schema for deps/<module_code>.json \u2014 the import list of what this module consumes from one dependency. Every manifest 'dependencies' entry needs one or the package will not validate, pack or install."
|
|
36628
|
+
),
|
|
36501
36629
|
schema(
|
|
36502
36630
|
"entity",
|
|
36503
36631
|
"Entity JSON schema",
|
|
@@ -36639,20 +36767,20 @@ var resources = [
|
|
|
36639
36767
|
columnSecurityExample("README.md", "Why entity views exist, what each of the two views demonstrates, and the five ways a view fails the install.")
|
|
36640
36768
|
];
|
|
36641
36769
|
function readVendored(rel2) {
|
|
36642
|
-
const p =
|
|
36643
|
-
if (!
|
|
36770
|
+
const p = path17.join(RESOURCES_DIR, rel2);
|
|
36771
|
+
if (!fs14.existsSync(p)) {
|
|
36644
36772
|
return `// Resource missing at build time: ${rel2}
|
|
36645
36773
|
// Run scripts/vendor-resources.sh in dforge-mcp to refresh from dForge-core.`;
|
|
36646
36774
|
}
|
|
36647
|
-
return
|
|
36775
|
+
return fs14.readFileSync(p, "utf8");
|
|
36648
36776
|
}
|
|
36649
36777
|
function readSkill(rel2) {
|
|
36650
|
-
const p =
|
|
36651
|
-
if (!
|
|
36778
|
+
const p = path17.join(SKILL_DIR, rel2);
|
|
36779
|
+
if (!fs14.existsSync(p)) {
|
|
36652
36780
|
return `// Skill resource missing: ${rel2}
|
|
36653
36781
|
// Expected under skills/dforge-mcp-author/ \u2014 check the package 'files' list.`;
|
|
36654
36782
|
}
|
|
36655
|
-
return
|
|
36783
|
+
return fs14.readFileSync(p, "utf8");
|
|
36656
36784
|
}
|
|
36657
36785
|
|
|
36658
36786
|
// src/server.ts
|
|
@@ -37020,7 +37148,7 @@ server.registerTool(
|
|
|
37020
37148
|
"dforge_dependency_add",
|
|
37021
37149
|
{
|
|
37022
37150
|
title: "Add a module dependency",
|
|
37023
|
-
description: "Add a dependency on another dForge module
|
|
37151
|
+
description: "Add a dependency on another dForge module. Writes BOTH halves the platform requires: the manifest.json entry and the deps/<module>.json contract declaring what this module consumes \u2014 a manifest dependency without a contract fails validate, pack and install. Name the consumed entities in `entities`; do not declare a dependency you cannot name one for, and do not depend on the system modules (admin, metadata, workspace) except to gate a minimum platform version.",
|
|
37024
37152
|
inputSchema: { ...dependencyAddSchema, ...applyInput }
|
|
37025
37153
|
},
|
|
37026
37154
|
envelope(dependencyAdd)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforge-core/dforge-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "MCP server for dForge module authoring. Exposes 34 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/iash44/dForge-core",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"install-skills": "node scripts/install-skills.mjs"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@dforge-core/dforge-cli": "^0.2.
|
|
38
|
+
"@dforge-core/dforge-cli": "^0.2.17",
|
|
39
39
|
"@dforge-core/metadata": "^0.0.22",
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
41
41
|
"zod": "^4.4.3"
|
|
@@ -35,7 +35,7 @@ Lives in: `logic/jobs.json` at the module root. Schema: [`docs/schemas/jobs.sche
|
|
|
35
35
|
| `concurrency` | No | `1` | Max parallel runs `[1, 5]`. `1` = single-instance. |
|
|
36
36
|
| `idempotencyKey` | No | | Template like `"{job_cd}:{run.month}"`. Successful run with same key blocks re-dispatch. |
|
|
37
37
|
| `timeZone` | No | tenant tz | IANA name. Falls back to `auth.tenant.time_zone`. |
|
|
38
|
-
| `params` | No | `{}` | Static parameters passed to the action as `__params`. For
|
|
38
|
+
| `params` | No | `{}` | Static parameters passed to the action as `__params`. Manifest-fixed — an admin cannot change them. For tenant-configurable values use module settings, resolved from the job's folder (below). |
|
|
39
39
|
| `enabled` | No | `true` | `false` fully disables — no scheduler, no manual trigger. |
|
|
40
40
|
| `paused` | No | `false` | Soft pause. Scheduler skips, but "Run now" still works. |
|
|
41
41
|
| `description` | No | | Human-readable description. |
|
|
@@ -58,10 +58,21 @@ minute hour day-of-month month day-of-week
|
|
|
58
58
|
Scheduled actions run with **no record bound**. Specifically:
|
|
59
59
|
|
|
60
60
|
- `user_id = 0` (system user sentinel)
|
|
61
|
-
-
|
|
61
|
+
- A folder, but no record — see **Folder context** below
|
|
62
62
|
- `notify()` writes to inbox only (no live SSE in Phase 1)
|
|
63
63
|
- `sendEmail()` raw mode only in Phase 1
|
|
64
64
|
|
|
65
|
+
### Folder context
|
|
66
|
+
|
|
67
|
+
A cron fire has no user, so nothing supplies a folder the way a request does. The scheduler binds one anyway, because `getSetting()` and `nextNumber()` resolve by walking a folder chain:
|
|
68
|
+
|
|
69
|
+
1. `scheduled_job.folder_id`, if an admin bound one (Admin → Scheduled Jobs → **Change folder**)
|
|
70
|
+
2. Otherwise the owning module's **root folder** — where the module's own settings page writes
|
|
71
|
+
|
|
72
|
+
So a setting an admin sets on the module's settings page reaches that module's jobs with no configuration. There is **no `folder` key in the manifest**: the binding is operator state, and module upgrades never overwrite it.
|
|
73
|
+
|
|
74
|
+
Inheritance stops at a folder whose `inherit_security` is false, exactly as it does for a user request.
|
|
75
|
+
|
|
65
76
|
**Record context is forbidden.** The install pipeline rejects any action whose compiled DSL references `[field]` or `for x in records { … }` — those compile to `__r.` / `__records.` which would `ReferenceError` at fire time. Refactor to operate via `select()` / `insert()` / `update()`:
|
|
66
77
|
|
|
67
78
|
```
|
|
@@ -18,9 +18,7 @@ Every dForge module has a `manifest.json` at its root. This file declares the mo
|
|
|
18
18
|
"category": "other",
|
|
19
19
|
"tags": ["tag1", "tag2"],
|
|
20
20
|
"icon": "bi-box",
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"admin": ">=0.0.1"
|
|
23
|
-
},
|
|
21
|
+
"dependencies": {},
|
|
24
22
|
"created": "2026-04-08",
|
|
25
23
|
"updated": "2026-04-08",
|
|
26
24
|
"entities": {
|
|
@@ -50,7 +48,7 @@ Every dForge module has a `manifest.json` at its root. This file declares the mo
|
|
|
50
48
|
| `category` | string | Marketplace category slug (e.g. `"sales"`, `"hr"`, `"other"`) |
|
|
51
49
|
| `tags` | string[] | Search tags |
|
|
52
50
|
| `icon` | string | Bootstrap icon name (e.g. `"bi-briefcase"`) or similar |
|
|
53
|
-
| `dependencies` | object | Map of `module_code → semver range` |
|
|
51
|
+
| `dependencies` | object | Map of `module_code → semver range`. Each entry also requires a `deps/<module_code>.json` contract — see [Dependencies](#dependencies). |
|
|
54
52
|
| `created`, `updated` | ISO date | Timestamps |
|
|
55
53
|
|
|
56
54
|
## Content declarations
|
|
@@ -107,23 +105,50 @@ Dependencies are a map of module codes to semver ranges. The installer checks th
|
|
|
107
105
|
|
|
108
106
|
```json
|
|
109
107
|
"dependencies": {
|
|
110
|
-
"admin": ">=0.0.1",
|
|
111
108
|
"fin": ">=0.1.0 <0.2.0"
|
|
112
109
|
}
|
|
113
110
|
```
|
|
114
111
|
|
|
115
|
-
**
|
|
112
|
+
**Declare a dependency only on a module you actually consume something from** — an entity you reference with a link column, extend, grant rights on, or read in a view, report, formula or action. Every entry you write here obliges you to write a matching `deps/<module>.json` contract (below), so a speculative dependency is not free.
|
|
113
|
+
|
|
114
|
+
**Do not depend on `admin`.** It — like `metadata` and `workspace` — is a system module, provisioned into every tenant before any other module installs. Depending on it buys nothing. The one legitimate reason to name a system module is to require a **minimum platform version** for a feature you use, e.g. `"metadata": ">=1.5.0"` for record-report attachments; that still needs a contract naming the entity the feature introduced.
|
|
116
115
|
|
|
117
116
|
**For bridge modules** (`crm-fin`, `wms-fin`, etc.), depend on both sides:
|
|
118
117
|
|
|
119
118
|
```json
|
|
120
119
|
"dependencies": {
|
|
121
|
-
"admin": ">=0.0.1",
|
|
122
120
|
"crm": ">=0.1.0",
|
|
123
121
|
"fin": ">=0.1.0"
|
|
124
122
|
}
|
|
125
123
|
```
|
|
126
124
|
|
|
125
|
+
### Dependency contracts (`deps/<module>.json`)
|
|
126
|
+
|
|
127
|
+
Every entry in `dependencies` needs a contract file at `deps/<module_code>.json` declaring what this module consumes from that dependency. `dforge_module_validate`, `dforge_module_pack` and the installer all fail without it. `dforge_dependency_add` writes both files for you.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"module": "fin",
|
|
132
|
+
"version": ">=0.1.0 <0.2.0",
|
|
133
|
+
"entities": {
|
|
134
|
+
"invoice": {
|
|
135
|
+
"pk": "invoice_id",
|
|
136
|
+
"use": ["ref:my_entity.invoice"],
|
|
137
|
+
"columns": {
|
|
138
|
+
"invoice_no": { "type": "varchar", "use": ["formula:my_entity.invoice_label"] }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- `version` must be the same range as the manifest's dependency entry.
|
|
146
|
+
- `entities` needs at least one entry; each needs a `pk` and a non-empty `use`.
|
|
147
|
+
- `columns` is optional — omit it when you touch no specific column of that entity. Declare only real storage columns you read, write or join on, at their **storage class** (`int8`, `varchar`, `numeric`, `bool`, `date`, `timestamptz`, …; a `cuid` PK is `int8`). Never declare a virtual column (`R`/`S`/`F`) or a column your own extension adds.
|
|
148
|
+
- `use` tokens are `<kind>:<symbol>` where kind is `ref`, `extends`, `formula`, `action`, `view`, `report`, `print` or `role`. `extends` must be fully qualified: `extends:parties.party`.
|
|
149
|
+
|
|
150
|
+
Full schema: `dforge://schema/deps`.
|
|
151
|
+
|
|
127
152
|
## Extension entities
|
|
128
153
|
|
|
129
154
|
Modules can extend entities owned by other modules. Declare these in `entities` with dotted keys:
|
|
@@ -142,7 +167,7 @@ The extension file has `"extends": "fin.invoice"` inside. See MODULE_CONVENTIONS
|
|
|
142
167
|
- **Do not** add a `translations` key (e.g. `"translations": { "en-US": "..." }`). There is **no** such manifest field — translation files are auto-discovered at `./translations/{locale}.json`, and non-English locales are declared in `supportedLocales` (English is never listed). The manifest schema is `additionalProperties: false`, so a stray `translations` key fails install.
|
|
143
168
|
- **Do not** put entity definitions inline. Always reference external files.
|
|
144
169
|
- **Do not** list sample or test files — only content that ships with the module.
|
|
145
|
-
- **Do not** include `system: true` unless this is a dForge platform module (`admin`, `metadata`). Regular modules omit it (defaults to `false`).
|
|
170
|
+
- **Do not** include `system: true` unless this is a dForge platform module (`admin`, `metadata`, `workspace`). Regular modules omit it (defaults to `false`).
|
|
146
171
|
- **Do not** hardcode absolute paths. Everything is relative to the manifest.
|
|
147
172
|
|
|
148
173
|
## Versioning notes
|
|
@@ -70,13 +70,17 @@ From number sequence patterns:
|
|
|
70
70
|
|
|
71
71
|
Settings resolve at runtime through the folder hierarchy:
|
|
72
72
|
|
|
73
|
-
1. The folder
|
|
73
|
+
1. The context's folder → its override (if any)
|
|
74
74
|
2. Parent folder → override (if any)
|
|
75
75
|
3. ... up the tree ...
|
|
76
76
|
4. Module default (from `settings.json`)
|
|
77
77
|
|
|
78
78
|
This means different folders can have different VAT rates, different prefixes, different currencies — without any code changes.
|
|
79
79
|
|
|
80
|
+
The walk stops at a folder whose `inherit_security` is false; that folder's own override still applies, but it does not inherit from its parent.
|
|
81
|
+
|
|
82
|
+
**Which folder is "the context's folder"** depends on what is reading. A user request uses the folder the user navigated to; an action uses the folder of the record or the queue entry; a **scheduled job** uses `scheduled_job.folder_id`, defaulting to its module's root folder. A job is the one context with no user, which is why it carries an explicit binding — without one it would see only the module default, and module defaults are not writable through the API (`settings.set` requires a folder).
|
|
83
|
+
|
|
80
84
|
## Field types supported in settings
|
|
81
85
|
|
|
82
86
|
Any field type that makes sense for a single scalar value:
|