directus-deploy-cli 0.1.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 +62 -0
- package/dist/cli.js +132 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff.js +31 -0
- package/dist/diff.js.map +1 -0
- package/dist/http.js +114 -0
- package/dist/http.js.map +1 -0
- package/dist/identity.js +124 -0
- package/dist/identity.js.map +1 -0
- package/dist/manifest.js +84 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp-server.js +197 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/reconcilers/collections.js +82 -0
- package/dist/reconcilers/collections.js.map +1 -0
- package/dist/reconcilers/fields.js +92 -0
- package/dist/reconcilers/fields.js.map +1 -0
- package/dist/reconcilers/flows.js +122 -0
- package/dist/reconcilers/flows.js.map +1 -0
- package/dist/reconcilers/migrations.js +182 -0
- package/dist/reconcilers/migrations.js.map +1 -0
- package/dist/reconcilers/operations.js +164 -0
- package/dist/reconcilers/operations.js.map +1 -0
- package/dist/reconcilers/permissions.js +87 -0
- package/dist/reconcilers/permissions.js.map +1 -0
- package/dist/reconcilers/policies.js +105 -0
- package/dist/reconcilers/policies.js.map +1 -0
- package/dist/reconcilers/register.js +183 -0
- package/dist/reconcilers/register.js.map +1 -0
- package/dist/reconcilers/relations.js +56 -0
- package/dist/reconcilers/relations.js.map +1 -0
- package/dist/reconcilers/roles.js +71 -0
- package/dist/reconcilers/roles.js.map +1 -0
- package/dist/reconcilers/seeds.js +162 -0
- package/dist/reconcilers/seeds.js.map +1 -0
- package/dist/report.js +16 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.js +160 -0
- package/dist/runner.js.map +1 -0
- package/dist/sanitize.js +31 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/sql.js +102 -0
- package/dist/sql.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# directus-deploy-cli
|
|
2
|
+
|
|
3
|
+
Per-entity, non-atomic Directus deployment tool. Reconciles code + config from a git repository to any Directus environment. Handles collections, fields, relations, roles, policies, permissions, flows, operations, raw-SQL migrations, register manifests, and seed data.
|
|
4
|
+
|
|
5
|
+
## Why not the built-in schema apply?
|
|
6
|
+
|
|
7
|
+
Directus's `/schema/apply` is atomic — one edge case ("field already exists", "relationship already associated", dangling `meta.group` ref, adopted-but-unregistered raw-SQL column) blocks every other change. In a repo with many contributors, multiple environments, and raw-SQL adopted tables, that atomic model becomes a firefight generator.
|
|
8
|
+
|
|
9
|
+
`directus-deploy` iterates each entity independently: `GET → POST | PATCH | SKIP`. If one entity fails, the other 900 still apply. Per-entity report tells you exactly what changed and why the skips/failures happened.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npm install -g directus-deploy-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
DIRECTUS_URL=https://your-directus DIRECTUS_TOKEN=... \
|
|
21
|
+
directus-deploy verify \
|
|
22
|
+
--snapshot-dir=./directus_config/snapshot \
|
|
23
|
+
--config-dir=./directus_config/collections \
|
|
24
|
+
--seed-dir=./directus_config/seed \
|
|
25
|
+
--migrations-dir=./migrations \
|
|
26
|
+
--register-dir=./migrations/register
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Commands: `plan` (dry-run report), `apply` (reconcile), `verify` (drift check; exits non-zero if apply would change anything).
|
|
30
|
+
|
|
31
|
+
## MCP server
|
|
32
|
+
|
|
33
|
+
Ships an MCP server exposing `directus_plan` / `directus_apply` / `directus_verify` as structured tools. Register in your Claude client's `.mcp.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"directus-deploy": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["<abs-path>/node_modules/directus-deploy-cli/dist/mcp-server.js"],
|
|
41
|
+
"env": { "DIRECTUS_URL": "...", "DIRECTUS_TOKEN": "..." }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## On-disk layout
|
|
48
|
+
|
|
49
|
+
Compatible with the `directus-sync`-style tree:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
directus_config/
|
|
53
|
+
snapshot/{collections,fields,relations}/*.json
|
|
54
|
+
collections/{roles,policies,permissions,flows,operations}.json
|
|
55
|
+
seed/*.json
|
|
56
|
+
migrations/*.sql
|
|
57
|
+
migrations/register/*.json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { run } from "./runner.js";
|
|
4
|
+
import { formatHuman, formatJson } from "./report.js";
|
|
5
|
+
import { createDirectusClient } from "./http.js";
|
|
6
|
+
const KNOWN_ENTITIES = [
|
|
7
|
+
"collections",
|
|
8
|
+
"fields",
|
|
9
|
+
"relations",
|
|
10
|
+
"roles",
|
|
11
|
+
"policies",
|
|
12
|
+
"permissions",
|
|
13
|
+
"flows",
|
|
14
|
+
"operations",
|
|
15
|
+
"migrations",
|
|
16
|
+
"seeds",
|
|
17
|
+
];
|
|
18
|
+
function parseEntities(csv) {
|
|
19
|
+
const parts = csv
|
|
20
|
+
.split(",")
|
|
21
|
+
.map((s) => s.trim())
|
|
22
|
+
.filter((s) => s.length > 0);
|
|
23
|
+
const set = new Set();
|
|
24
|
+
for (const p of parts) {
|
|
25
|
+
if (!KNOWN_ENTITIES.includes(p)) {
|
|
26
|
+
throw new Error(`unknown entity kind '${p}' (expected: ${KNOWN_ENTITIES.join(", ")})`);
|
|
27
|
+
}
|
|
28
|
+
set.add(p);
|
|
29
|
+
}
|
|
30
|
+
return set;
|
|
31
|
+
}
|
|
32
|
+
function readCommon(flags) {
|
|
33
|
+
const url = flags.url ?? process.env.DIRECTUS_URL;
|
|
34
|
+
const token = flags.token ?? process.env.DIRECTUS_TOKEN;
|
|
35
|
+
if (!url)
|
|
36
|
+
throw new Error("--url or DIRECTUS_URL required");
|
|
37
|
+
if (!token)
|
|
38
|
+
throw new Error("--token or DIRECTUS_TOKEN required");
|
|
39
|
+
const target = flags.target ?? new URL(url).hostname;
|
|
40
|
+
const entities = parseEntities(flags.entities);
|
|
41
|
+
const onlyCollections = flags.onlyCollections
|
|
42
|
+
? new Set(flags.onlyCollections.split(",").map((s) => s.trim()).filter(Boolean))
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
url,
|
|
46
|
+
token,
|
|
47
|
+
target,
|
|
48
|
+
entities,
|
|
49
|
+
onlyCollections,
|
|
50
|
+
snapshotDir: flags.snapshotDir,
|
|
51
|
+
configDir: flags.configDir,
|
|
52
|
+
registerDir: flags.registerDir,
|
|
53
|
+
migrationsDir: flags.migrationsDir,
|
|
54
|
+
seedDir: flags.seedDir,
|
|
55
|
+
json: Boolean(flags.json),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function execute(mode, flags) {
|
|
59
|
+
const common = readCommon(flags);
|
|
60
|
+
const client = createDirectusClient({ baseUrl: common.url, token: common.token });
|
|
61
|
+
const opts = { dryRun: mode.dryRun, onlyCollections: common.onlyCollections };
|
|
62
|
+
const report = await run({
|
|
63
|
+
target: common.target,
|
|
64
|
+
paths: {
|
|
65
|
+
snapshotDir: common.snapshotDir,
|
|
66
|
+
configDir: common.configDir,
|
|
67
|
+
registerDir: common.registerDir,
|
|
68
|
+
},
|
|
69
|
+
migrationsDir: common.migrationsDir,
|
|
70
|
+
seedDir: common.seedDir,
|
|
71
|
+
client,
|
|
72
|
+
opts,
|
|
73
|
+
entities: common.entities,
|
|
74
|
+
});
|
|
75
|
+
if (common.json) {
|
|
76
|
+
process.stdout.write(formatJson(report) + "\n");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
process.stdout.write(formatHuman(report) + "\n");
|
|
80
|
+
}
|
|
81
|
+
if (report.counts.failed > 0)
|
|
82
|
+
return 1;
|
|
83
|
+
if (mode.strict) {
|
|
84
|
+
// Verify: any drift (created/updated) means the target didn't match git.
|
|
85
|
+
// `skipped` is intentional (adopted-but-unregistered raw-SQL columns,
|
|
86
|
+
// register-manifest collections) — never treated as drift.
|
|
87
|
+
if (report.counts.created > 0 || report.counts.updated > 0) {
|
|
88
|
+
process.stderr.write(`verify: drift detected (${report.counts.created} would-create, ${report.counts.updated} would-update)\n`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const program = new Command();
|
|
95
|
+
program
|
|
96
|
+
.name("directus-deploy")
|
|
97
|
+
.description("Reconcile a Directus environment to the state described in directus_config/snapshot/. Per-entity, non-atomic.")
|
|
98
|
+
.version("0.1.0");
|
|
99
|
+
function attachCommon(cmd) {
|
|
100
|
+
return cmd
|
|
101
|
+
.option("--url <url>", "Directus base URL (env: DIRECTUS_URL)")
|
|
102
|
+
.option("--token <token>", "Directus admin token (env: DIRECTUS_TOKEN)")
|
|
103
|
+
.option("--target <label>", "friendly label for logs (default: URL hostname)")
|
|
104
|
+
.option("--entities <csv>", `comma-separated subset of ${KNOWN_ENTITIES.join(",")}`, KNOWN_ENTITIES.join(","))
|
|
105
|
+
.option("--only-collections <csv>", "restrict run to these collection names")
|
|
106
|
+
.option("--snapshot-dir <path>", "path to directus_config/snapshot", "./directus_config/snapshot")
|
|
107
|
+
.option("--config-dir <path>", "path to directus_config/collections (holds policies.json, permissions.json, roles.json, …)", "./directus_config/collections")
|
|
108
|
+
.option("--register-dir <path>", "path to migrations/register", "./migrations/register")
|
|
109
|
+
.option("--seed-dir <path>", "path to directus_config/seed (Tractr-style {collection, meta, data} files)", "./directus_config/seed")
|
|
110
|
+
.option("--migrations-dir <path>", "path to migrations/*.sql (raw SQL, tracked in lola_deploy_migrations)", "./migrations")
|
|
111
|
+
.option("--json", "emit JSON report instead of human-readable");
|
|
112
|
+
}
|
|
113
|
+
attachCommon(program.command("plan"))
|
|
114
|
+
.description("Dry-run: report what would change without writing.")
|
|
115
|
+
.action(async (_, cmd) => {
|
|
116
|
+
process.exit(await execute({ dryRun: true }, cmd.optsWithGlobals()));
|
|
117
|
+
});
|
|
118
|
+
attachCommon(program.command("apply"))
|
|
119
|
+
.description("Apply the desired state to the target env.")
|
|
120
|
+
.action(async (_, cmd) => {
|
|
121
|
+
process.exit(await execute({ dryRun: false }, cmd.optsWithGlobals()));
|
|
122
|
+
});
|
|
123
|
+
attachCommon(program.command("verify"))
|
|
124
|
+
.description("Post-apply drift check. Runs plan-mode and exits non-zero if any entity would be created or updated (idempotency guard for CI).")
|
|
125
|
+
.action(async (_, cmd) => {
|
|
126
|
+
process.exit(await execute({ dryRun: true, strict: true }, cmd.optsWithGlobals()));
|
|
127
|
+
});
|
|
128
|
+
program.parseAsync(process.argv).catch((e) => {
|
|
129
|
+
process.stderr.write(`directus-deploy: ${e.message}\n`);
|
|
130
|
+
process.exit(2);
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGjD,MAAM,cAAc,GAAG;IACrB,aAAa;IACb,QAAQ;IACR,WAAW;IACX,OAAO;IACP,UAAU;IACV,aAAa;IACb,OAAO;IACP,YAAY;IACZ,YAAY;IACZ,OAAO;CACC,CAAC;AAiBX,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAE,cAAoC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,gBAAgB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzF,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,CAAW,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,UAAU,CAAC,KAAkB;IAapC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACxD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IACrD,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe;QAC3C,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChF,CAAC,CAAC,SAAS,CAAC;IACd,OAAO;QACL,GAAG;QACH,KAAK;QACL,MAAM;QACN,QAAQ;QACR,eAAe;QACf,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;KAC1B,CAAC;AACJ,CAAC;AAOD,KAAK,UAAU,OAAO,CAAC,IAAoB,EAAE,KAAkB;IAC7D,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,oBAAoB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAClF,MAAM,IAAI,GAAiB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC;IAC5F,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE;YACL,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC;QACD,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM;QACN,IAAI;QACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACvC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,yEAAyE;QACzE,sEAAsE;QACtE,2DAA2D;QAC3D,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAC3D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2BAA2B,MAAM,CAAC,MAAM,CAAC,OAAO,kBAAkB,MAAM,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAC1G,CAAC;YACF,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,OAAO;KACJ,IAAI,CAAC,iBAAiB,CAAC;KACvB,WAAW,CACV,+GAA+G,CAChH;KACA,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,SAAS,YAAY,CAAC,GAAY;IAChC,OAAO,GAAG;SACP,MAAM,CAAC,aAAa,EAAE,uCAAuC,CAAC;SAC9D,MAAM,CAAC,iBAAiB,EAAE,4CAA4C,CAAC;SACvE,MAAM,CAAC,kBAAkB,EAAE,iDAAiD,CAAC;SAC7E,MAAM,CACL,kBAAkB,EAClB,6BAA6B,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EACvD,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CACzB;SACA,MAAM,CAAC,0BAA0B,EAAE,wCAAwC,CAAC;SAC5E,MAAM,CACL,uBAAuB,EACvB,kCAAkC,EAClC,4BAA4B,CAC7B;SACA,MAAM,CACL,qBAAqB,EACrB,4FAA4F,EAC5F,+BAA+B,CAChC;SACA,MAAM,CACL,uBAAuB,EACvB,6BAA6B,EAC7B,uBAAuB,CACxB;SACA,MAAM,CACL,mBAAmB,EACnB,4EAA4E,EAC5E,wBAAwB,CACzB;SACA,MAAM,CACL,yBAAyB,EACzB,uEAAuE,EACvE,cAAc,CACf;SACA,MAAM,CAAC,QAAQ,EAAE,4CAA4C,CAAC,CAAC;AACpE,CAAC;AAED,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;KAClC,WAAW,CAAC,oDAAoD,CAAC;KACjE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;IACvB,OAAO,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEL,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;KACnC,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;IACvB,OAAO,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC;AAEL,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;KACpC,WAAW,CACV,iIAAiI,CAClI;KACA,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;IACvB,OAAO,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AACrF,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAqB,CAAW,CAAC,OAAO,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// diff_subset — true iff desired has any key/value that differs from (or is
|
|
2
|
+
// missing in) actual. The live Directus row usually carries server-only keys
|
|
3
|
+
// we don't manage (id, timestamps); we ignore those by asymmetric compare.
|
|
4
|
+
export function diffSubset(desired, actual) {
|
|
5
|
+
if (desired === null || desired === undefined) {
|
|
6
|
+
// A null/undefined desired doesn't force a diff — the caller should
|
|
7
|
+
// only pass in the block they intend to reconcile.
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (typeof desired !== "object") {
|
|
11
|
+
return desired !== actual;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(desired)) {
|
|
14
|
+
if (!Array.isArray(actual))
|
|
15
|
+
return true;
|
|
16
|
+
if (desired.length !== actual.length)
|
|
17
|
+
return true;
|
|
18
|
+
return desired.some((v, i) => diffSubset(v, actual[i]));
|
|
19
|
+
}
|
|
20
|
+
if (actual === null || actual === undefined || typeof actual !== "object" || Array.isArray(actual)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const dObj = desired;
|
|
24
|
+
const aObj = actual;
|
|
25
|
+
for (const [k, v] of Object.entries(dObj)) {
|
|
26
|
+
if (diffSubset(v, aObj[k]))
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAE3E,MAAM,UAAU,UAAU,CAAC,OAAgB,EAAE,MAAe;IAC1D,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9C,oEAAoE;QACpE,mDAAmD;QACnD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,OAAO,KAAK,MAAM,CAAC;IAC5B,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAClD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnG,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,OAAkC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAiC,CAAC;IAC/C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,IAAI,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
function toErr(url, status, body) {
|
|
2
|
+
const e = new Error(`${status} ${url} :: ${body}`);
|
|
3
|
+
e.status = status;
|
|
4
|
+
e.body = body;
|
|
5
|
+
return e;
|
|
6
|
+
}
|
|
7
|
+
// Small VMs (test.lola.market) 503 with `Service "api" is unavailable. Under
|
|
8
|
+
// pressure.` during bulk apply. Retry with exponential backoff — the caller
|
|
9
|
+
// has no way to distinguish this from a real outage.
|
|
10
|
+
const RETRY_STATUSES = new Set([503, 502, 504]);
|
|
11
|
+
const MAX_RETRIES = 6;
|
|
12
|
+
function backoffMs(attempt) {
|
|
13
|
+
// 500, 1000, 2000, 4000, 8000, 16000 ms — cumulative ~31s
|
|
14
|
+
return 500 * Math.pow(2, attempt);
|
|
15
|
+
}
|
|
16
|
+
async function sleep(ms) {
|
|
17
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
18
|
+
}
|
|
19
|
+
export function createDirectusClient(cfg) {
|
|
20
|
+
const base = cfg.baseUrl.replace(/\/+$/, "");
|
|
21
|
+
const fetchImpl = cfg.fetch ?? globalThis.fetch;
|
|
22
|
+
const headers = {
|
|
23
|
+
Authorization: `Bearer ${cfg.token}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
};
|
|
27
|
+
async function readJson(r) {
|
|
28
|
+
if (r.status === 204)
|
|
29
|
+
return {};
|
|
30
|
+
const text = await r.text();
|
|
31
|
+
if (!text)
|
|
32
|
+
return {};
|
|
33
|
+
try {
|
|
34
|
+
const j = JSON.parse(text);
|
|
35
|
+
return (j && typeof j === "object" ? j : {}) ?? {};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw toErr(r.url, r.status, text);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function fetchWithRetry(url, init) {
|
|
42
|
+
let lastErr = undefined;
|
|
43
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
const r = await fetchImpl(url, init);
|
|
46
|
+
if (!RETRY_STATUSES.has(r.status))
|
|
47
|
+
return r;
|
|
48
|
+
if (attempt === MAX_RETRIES)
|
|
49
|
+
return r;
|
|
50
|
+
// Drain body so the connection is reusable.
|
|
51
|
+
try {
|
|
52
|
+
await r.text();
|
|
53
|
+
}
|
|
54
|
+
catch { /* ignore */ }
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
// Network error (ECONNRESET/EAI_AGAIN) — retry the same window.
|
|
58
|
+
lastErr = e;
|
|
59
|
+
if (attempt === MAX_RETRIES)
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
await sleep(backoffMs(attempt));
|
|
63
|
+
}
|
|
64
|
+
// Unreachable — the loop returns or throws.
|
|
65
|
+
throw lastErr instanceof Error ? lastErr : new Error("fetch retry exhausted");
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
async get(path) {
|
|
69
|
+
const r = await fetchWithRetry(base + path, { headers });
|
|
70
|
+
if (r.status === 404 || r.status === 403)
|
|
71
|
+
return null;
|
|
72
|
+
if (!r.ok)
|
|
73
|
+
throw toErr(r.url, r.status, await r.text());
|
|
74
|
+
const j = await readJson(r);
|
|
75
|
+
const data = j.data;
|
|
76
|
+
if (Array.isArray(data))
|
|
77
|
+
return data;
|
|
78
|
+
return data && typeof data === "object" ? data : null;
|
|
79
|
+
},
|
|
80
|
+
async post(path, body) {
|
|
81
|
+
const r = await fetchWithRetry(base + path, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers,
|
|
84
|
+
body: JSON.stringify(body ?? {}),
|
|
85
|
+
});
|
|
86
|
+
if (!r.ok)
|
|
87
|
+
throw toErr(r.url, r.status, await r.text());
|
|
88
|
+
const j = await readJson(r);
|
|
89
|
+
return j.data ?? {};
|
|
90
|
+
},
|
|
91
|
+
async patch(path, body) {
|
|
92
|
+
const r = await fetchWithRetry(base + path, {
|
|
93
|
+
method: "PATCH",
|
|
94
|
+
headers,
|
|
95
|
+
body: JSON.stringify(body ?? {}),
|
|
96
|
+
});
|
|
97
|
+
if (!r.ok)
|
|
98
|
+
throw toErr(r.url, r.status, await r.text());
|
|
99
|
+
const j = await readJson(r);
|
|
100
|
+
return j.data ?? {};
|
|
101
|
+
},
|
|
102
|
+
async postRaw(path, body) {
|
|
103
|
+
const r = await fetchWithRetry(base + path, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers,
|
|
106
|
+
body: JSON.stringify(body ?? {}),
|
|
107
|
+
});
|
|
108
|
+
if (!r.ok)
|
|
109
|
+
throw toErr(r.url, r.status, await r.text());
|
|
110
|
+
return await readJson(r);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Resolve Tractr's `_syncId` foreign keys inside a payload to the real
|
|
2
|
+
// server-side ids/UUIDs, using name-based lookup on server state.
|
|
3
|
+
//
|
|
4
|
+
// The backend's directus_config/ has cross-references like:
|
|
5
|
+
// permission.policy = "_sync_default_public_policy" // policy _syncId
|
|
6
|
+
// policy.roles = [{ role: "_sync_default_admin_role", … }] // role _syncId
|
|
7
|
+
//
|
|
8
|
+
// The map from `_syncId` → `name` is defined by the local JSON files. From
|
|
9
|
+
// there we look up the server id by name. That's how we stay compatible with
|
|
10
|
+
// existing snapshots without depending on Tractr's directus_sync_id_map table.
|
|
11
|
+
function collectNames(local) {
|
|
12
|
+
const map = new Map();
|
|
13
|
+
for (const row of local) {
|
|
14
|
+
const syncId = String(row._syncId ?? "");
|
|
15
|
+
const name = String(row.name ?? "");
|
|
16
|
+
if (syncId && name)
|
|
17
|
+
map.set(syncId, name);
|
|
18
|
+
}
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
async function listAll(client, path) {
|
|
22
|
+
// Directus REST returns paginated {data: [...]} at the collection root.
|
|
23
|
+
// For our small system-collection sizes (policies ≤ ~50, roles ≤ ~20), one
|
|
24
|
+
// GET with limit=-1 is fine.
|
|
25
|
+
const raw = await client.get(`${path}?limit=-1&fields=id,name`);
|
|
26
|
+
if (raw === null)
|
|
27
|
+
return [];
|
|
28
|
+
// client.get returns .data already unwrapped. But when the endpoint returns
|
|
29
|
+
// an array-of-records that arrives as a plain array cast to
|
|
30
|
+
// Record<string, unknown> by our current typing; the runtime shape is
|
|
31
|
+
// actually unknown[]. Handle both.
|
|
32
|
+
if (Array.isArray(raw))
|
|
33
|
+
return raw;
|
|
34
|
+
const data = raw.data;
|
|
35
|
+
return Array.isArray(data) ? data : [];
|
|
36
|
+
}
|
|
37
|
+
function indexByName(rows) {
|
|
38
|
+
const map = new Map();
|
|
39
|
+
for (const r of rows) {
|
|
40
|
+
const name = String(r.name ?? "");
|
|
41
|
+
const id = String(r.id ?? "");
|
|
42
|
+
if (name && id)
|
|
43
|
+
map.set(name, id);
|
|
44
|
+
}
|
|
45
|
+
return map;
|
|
46
|
+
}
|
|
47
|
+
function collectOpSyncIdIndex(localOps) {
|
|
48
|
+
const map = new Map();
|
|
49
|
+
for (const op of localOps) {
|
|
50
|
+
const sync = String(op._syncId ?? "");
|
|
51
|
+
const flow = String(op.flow ?? "");
|
|
52
|
+
const key = String(op.key ?? "");
|
|
53
|
+
if (sync && flow && key)
|
|
54
|
+
map.set(sync, { flowSyncId: flow, key });
|
|
55
|
+
}
|
|
56
|
+
return map;
|
|
57
|
+
}
|
|
58
|
+
function indexServerOps(serverOps) {
|
|
59
|
+
const map = new Map();
|
|
60
|
+
for (const op of serverOps) {
|
|
61
|
+
const id = String(op.id ?? "");
|
|
62
|
+
const flow = String(op.flow ?? "");
|
|
63
|
+
const key = String(op.key ?? "");
|
|
64
|
+
if (id && flow && key)
|
|
65
|
+
map.set(`${flow}::${key}`, id);
|
|
66
|
+
}
|
|
67
|
+
return map;
|
|
68
|
+
}
|
|
69
|
+
export async function buildIdentity(client, localPolicies, localRoles, localFlows = [], localOps = []) {
|
|
70
|
+
const [serverPolicies, serverRoles, serverFlows, serverOps] = await Promise.all([
|
|
71
|
+
listAll(client, "/policies"),
|
|
72
|
+
listAll(client, "/roles"),
|
|
73
|
+
localFlows.length
|
|
74
|
+
? client
|
|
75
|
+
.get("/flows?limit=-1&fields=id,name")
|
|
76
|
+
.then((r) => (Array.isArray(r) ? r : []))
|
|
77
|
+
: Promise.resolve([]),
|
|
78
|
+
localOps.length
|
|
79
|
+
? client
|
|
80
|
+
.get("/operations?limit=-1&fields=id,flow,key")
|
|
81
|
+
.then((r) => (Array.isArray(r) ? r : []))
|
|
82
|
+
: Promise.resolve([]),
|
|
83
|
+
]);
|
|
84
|
+
return {
|
|
85
|
+
policySyncIdToName: collectNames(localPolicies),
|
|
86
|
+
roleSyncIdToName: collectNames(localRoles),
|
|
87
|
+
serverPolicyIdByName: indexByName(serverPolicies),
|
|
88
|
+
serverRoleIdByName: indexByName(serverRoles),
|
|
89
|
+
flowSyncIdToName: collectNames(localFlows),
|
|
90
|
+
serverFlowIdByName: indexByName(serverFlows),
|
|
91
|
+
opSyncIdToFlowAndKey: collectOpSyncIdIndex(localOps),
|
|
92
|
+
serverOpIdByFlowIdAndKey: indexServerOps(serverOps),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function resolvePolicySyncIdToServerId(syncId, idx) {
|
|
96
|
+
const name = idx.policySyncIdToName.get(syncId);
|
|
97
|
+
if (!name)
|
|
98
|
+
return null;
|
|
99
|
+
return idx.serverPolicyIdByName.get(name) ?? null;
|
|
100
|
+
}
|
|
101
|
+
export function resolveRoleSyncIdToServerId(syncId, idx) {
|
|
102
|
+
const name = idx.roleSyncIdToName.get(syncId);
|
|
103
|
+
if (!name)
|
|
104
|
+
return null;
|
|
105
|
+
return idx.serverRoleIdByName.get(name) ?? null;
|
|
106
|
+
}
|
|
107
|
+
export function resolveFlowSyncIdToServerId(syncId, idx) {
|
|
108
|
+
const name = idx.flowSyncIdToName.get(syncId);
|
|
109
|
+
if (!name)
|
|
110
|
+
return null;
|
|
111
|
+
return idx.serverFlowIdByName.get(name) ?? null;
|
|
112
|
+
}
|
|
113
|
+
// Operation identity: local _syncId → (flow_syncId, key) → we resolve
|
|
114
|
+
// flow_syncId → server flow id → look up server op by (flow_id, key).
|
|
115
|
+
export function resolveOpSyncIdToServerId(syncId, idx) {
|
|
116
|
+
const local = idx.opSyncIdToFlowAndKey.get(syncId);
|
|
117
|
+
if (!local)
|
|
118
|
+
return null;
|
|
119
|
+
const serverFlowId = resolveFlowSyncIdToServerId(local.flowSyncId, idx);
|
|
120
|
+
if (!serverFlowId)
|
|
121
|
+
return null;
|
|
122
|
+
return idx.serverOpIdByFlowIdAndKey.get(`${serverFlowId}::${local.key}`) ?? null;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Load the snapshot files that describe the desired state. Reads the same
|
|
2
|
+
// on-disk shape directus-sync-style repo uses under directus_config/, so we
|
|
3
|
+
// can drop this tool into the existing repo without a migration.
|
|
4
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
async function readJsonDir(dir) {
|
|
7
|
+
let entries;
|
|
8
|
+
try {
|
|
9
|
+
entries = await readdir(dir);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const files = entries.filter((f) => f.endsWith(".json")).sort();
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const f of files) {
|
|
17
|
+
const text = await readFile(join(dir, f), "utf8");
|
|
18
|
+
out.push(JSON.parse(text));
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
async function readCollectionSubdirs(dir) {
|
|
23
|
+
const map = new Map();
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = await readdir(dir, { withFileTypes: false });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return map;
|
|
30
|
+
}
|
|
31
|
+
for (const name of entries.sort()) {
|
|
32
|
+
const rows = await readJsonDir(join(dir, name));
|
|
33
|
+
if (rows.length)
|
|
34
|
+
map.set(name, rows);
|
|
35
|
+
}
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
async function readJsonArray(path) {
|
|
39
|
+
try {
|
|
40
|
+
const text = await readFile(path, "utf8");
|
|
41
|
+
const parsed = JSON.parse(text);
|
|
42
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function readRegisterManifests(dir) {
|
|
49
|
+
const set = new Set();
|
|
50
|
+
let entries;
|
|
51
|
+
try {
|
|
52
|
+
entries = await readdir(dir);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return set;
|
|
56
|
+
}
|
|
57
|
+
for (const f of entries) {
|
|
58
|
+
if (!f.endsWith(".json"))
|
|
59
|
+
continue;
|
|
60
|
+
try {
|
|
61
|
+
const j = JSON.parse(await readFile(join(dir, f), "utf8"));
|
|
62
|
+
if (j.table)
|
|
63
|
+
set.add(j.table);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Ignore malformed manifests here — the caller reports them separately.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return set;
|
|
70
|
+
}
|
|
71
|
+
export async function loadSnapshot(paths) {
|
|
72
|
+
return {
|
|
73
|
+
collections: await readJsonDir(join(paths.snapshotDir, "collections")),
|
|
74
|
+
fieldsByCollection: await readCollectionSubdirs(join(paths.snapshotDir, "fields")),
|
|
75
|
+
relationsByCollection: await readCollectionSubdirs(join(paths.snapshotDir, "relations")),
|
|
76
|
+
policies: await readJsonArray(join(paths.configDir, "policies.json")),
|
|
77
|
+
roles: await readJsonArray(join(paths.configDir, "roles.json")),
|
|
78
|
+
permissions: await readJsonArray(join(paths.configDir, "permissions.json")),
|
|
79
|
+
flows: await readJsonArray(join(paths.configDir, "flows.json")),
|
|
80
|
+
operations: await readJsonArray(join(paths.configDir, "operations.json")),
|
|
81
|
+
registerManifests: await readRegisterManifests(paths.registerDir),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|