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.
Files changed (46) hide show
  1. package/README.md +62 -0
  2. package/dist/cli.js +132 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/diff.js +31 -0
  5. package/dist/diff.js.map +1 -0
  6. package/dist/http.js +114 -0
  7. package/dist/http.js.map +1 -0
  8. package/dist/identity.js +124 -0
  9. package/dist/identity.js.map +1 -0
  10. package/dist/manifest.js +84 -0
  11. package/dist/manifest.js.map +1 -0
  12. package/dist/mcp-server.js +197 -0
  13. package/dist/mcp-server.js.map +1 -0
  14. package/dist/reconcilers/collections.js +82 -0
  15. package/dist/reconcilers/collections.js.map +1 -0
  16. package/dist/reconcilers/fields.js +92 -0
  17. package/dist/reconcilers/fields.js.map +1 -0
  18. package/dist/reconcilers/flows.js +122 -0
  19. package/dist/reconcilers/flows.js.map +1 -0
  20. package/dist/reconcilers/migrations.js +182 -0
  21. package/dist/reconcilers/migrations.js.map +1 -0
  22. package/dist/reconcilers/operations.js +164 -0
  23. package/dist/reconcilers/operations.js.map +1 -0
  24. package/dist/reconcilers/permissions.js +87 -0
  25. package/dist/reconcilers/permissions.js.map +1 -0
  26. package/dist/reconcilers/policies.js +105 -0
  27. package/dist/reconcilers/policies.js.map +1 -0
  28. package/dist/reconcilers/register.js +183 -0
  29. package/dist/reconcilers/register.js.map +1 -0
  30. package/dist/reconcilers/relations.js +56 -0
  31. package/dist/reconcilers/relations.js.map +1 -0
  32. package/dist/reconcilers/roles.js +71 -0
  33. package/dist/reconcilers/roles.js.map +1 -0
  34. package/dist/reconcilers/seeds.js +162 -0
  35. package/dist/reconcilers/seeds.js.map +1 -0
  36. package/dist/report.js +16 -0
  37. package/dist/report.js.map +1 -0
  38. package/dist/runner.js +160 -0
  39. package/dist/runner.js.map +1 -0
  40. package/dist/sanitize.js +31 -0
  41. package/dist/sanitize.js.map +1 -0
  42. package/dist/sql.js +102 -0
  43. package/dist/sql.js.map +1 -0
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +45 -0
@@ -0,0 +1,182 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join, extname, basename } from "node:path";
3
+ import { splitSql } from "../sql.js";
4
+ // Apply migrations/*.sql files idempotently. Tracks applied set in a
5
+ // `lola_deploy_migrations` table on the target so we don't re-run history on
6
+ // every deploy (the backend's convention is that migrations are idempotent
7
+ // via `IF NOT EXISTS`, so re-running is safe, but pointless and slow).
8
+ //
9
+ // Uses Directus's /raw-query/execute endpoint — the same one the backend has
10
+ // used for years. Not Postgres-agnostic, but nothing else in this repo is
11
+ // either (we're a Directus deploy tool). Callers on a fresh env need the
12
+ // endpoint deployed as an extension; we detect the 404 and skip cleanly.
13
+ //
14
+ // Requires: /raw-query/execute deployed on the target. We PROBE it first so a
15
+ // missing extension yields a clean skip rather than N confusing errors.
16
+ const TRACKER_TABLE = "lola_deploy_migrations";
17
+ const CREATE_TRACKER = `
18
+ CREATE TABLE IF NOT EXISTS ${TRACKER_TABLE} (
19
+ filename TEXT PRIMARY KEY,
20
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
21
+ );
22
+ `;
23
+ async function rawQuery(client, sql) {
24
+ try {
25
+ const r = (await client.postRaw("/raw-query/execute", { query: sql }));
26
+ if (r === null || typeof r !== "object")
27
+ return null;
28
+ return r;
29
+ }
30
+ catch (e) {
31
+ // 404 = extension not installed
32
+ const msg = e.message;
33
+ if (msg.includes(" 404 "))
34
+ return null;
35
+ throw e;
36
+ }
37
+ }
38
+ async function fetchAppliedFilenames(client) {
39
+ const r = await rawQuery(client, `SELECT filename FROM ${TRACKER_TABLE}`);
40
+ if (r === null)
41
+ return null;
42
+ if (!r.success)
43
+ return null;
44
+ const first = r.results?.[0];
45
+ if (!first?.success)
46
+ return null;
47
+ const rows = first.data ?? [];
48
+ const set = new Set();
49
+ for (const row of rows) {
50
+ if (row && typeof row === "object" && "filename" in row) {
51
+ set.add(String(row.filename));
52
+ }
53
+ }
54
+ return set;
55
+ }
56
+ export async function reconcileMigrations(input) {
57
+ const results = [];
58
+ // Probe for the endpoint. Failing here is not an error — a fresh env just
59
+ // hasn't installed the raw-query extension yet.
60
+ const probe = await rawQuery(input.client, "SELECT 1 AS ok");
61
+ if (probe === null) {
62
+ results.push({
63
+ kind: "migrations",
64
+ label: "migrations",
65
+ action: "skipped",
66
+ reason: "/raw-query/execute not available on this target",
67
+ });
68
+ return results;
69
+ }
70
+ let entries;
71
+ try {
72
+ entries = await readdir(input.migrationsDir);
73
+ }
74
+ catch {
75
+ return results; // no migrations dir → nothing to apply
76
+ }
77
+ const files = entries.filter((f) => extname(f) === ".sql" && !f.startsWith("_")).sort();
78
+ if (files.length === 0)
79
+ return results;
80
+ // Ensure the tracker exists (idempotent).
81
+ if (!input.opts.dryRun) {
82
+ const created = await rawQuery(input.client, CREATE_TRACKER);
83
+ if (!created?.success) {
84
+ results.push({
85
+ kind: "migrations",
86
+ label: `migrations/${TRACKER_TABLE}`,
87
+ action: "failed",
88
+ reason: "could not create tracker table",
89
+ });
90
+ return results;
91
+ }
92
+ }
93
+ const applied = (await fetchAppliedFilenames(input.client)) ?? new Set();
94
+ for (const filename of files) {
95
+ const label = `migrations/${filename}`;
96
+ if (applied.has(filename)) {
97
+ results.push({ kind: "migrations", label, action: "unchanged" });
98
+ continue;
99
+ }
100
+ const full = join(input.migrationsDir, filename);
101
+ let raw;
102
+ try {
103
+ raw = await readFile(full, "utf8");
104
+ }
105
+ catch (e) {
106
+ results.push({ kind: "migrations", label, action: "failed", reason: e.message });
107
+ continue;
108
+ }
109
+ // Filter out statements that are purely comments/whitespace after their
110
+ // leading `--` or `/* ... */` blocks. Anything else (including statements
111
+ // that BEGIN with a comment but have real SQL after) stays.
112
+ const stripLeadingComments = (s) => {
113
+ let i = 0;
114
+ // repeat until no comments remain at the head
115
+ // eslint-disable-next-line no-constant-condition
116
+ while (true) {
117
+ while (i < s.length && /\s/.test(s[i]))
118
+ i += 1;
119
+ if (s[i] === "-" && s[i + 1] === "-") {
120
+ const nl = s.indexOf("\n", i);
121
+ if (nl === -1)
122
+ return "";
123
+ i = nl + 1;
124
+ continue;
125
+ }
126
+ if (s[i] === "/" && s[i + 1] === "*") {
127
+ const end = s.indexOf("*/", i);
128
+ if (end === -1)
129
+ return "";
130
+ i = end + 2;
131
+ continue;
132
+ }
133
+ break;
134
+ }
135
+ return s.slice(i);
136
+ };
137
+ const statements = splitSql(raw).filter((s) => {
138
+ const stripped = stripLeadingComments(s.trim()).trim();
139
+ return stripped.length > 0 && stripped !== ";";
140
+ });
141
+ if (statements.length === 0) {
142
+ results.push({ kind: "migrations", label, action: "skipped", reason: "no executable statements" });
143
+ continue;
144
+ }
145
+ if (input.opts.dryRun) {
146
+ results.push({
147
+ kind: "migrations",
148
+ label,
149
+ action: "created",
150
+ reason: `${statements.length} statement(s) would apply`,
151
+ });
152
+ continue;
153
+ }
154
+ // Apply. If any statement fails, report and stop (don't record as applied).
155
+ let failedReason = null;
156
+ for (const stmt of statements) {
157
+ const r = await rawQuery(input.client, stmt);
158
+ const inner = r?.results?.[0];
159
+ if (!r?.success || !inner?.success) {
160
+ failedReason = inner?.error ?? "unknown error";
161
+ break;
162
+ }
163
+ }
164
+ if (failedReason) {
165
+ results.push({ kind: "migrations", label, action: "failed", reason: failedReason });
166
+ continue;
167
+ }
168
+ const recorded = await rawQuery(input.client, `INSERT INTO ${TRACKER_TABLE} (filename) VALUES ('${basename(filename).replace(/'/g, "''")}')`);
169
+ if (!recorded?.success) {
170
+ results.push({
171
+ kind: "migrations",
172
+ label,
173
+ action: "failed",
174
+ reason: "applied statements but could not record in tracker",
175
+ });
176
+ continue;
177
+ }
178
+ results.push({ kind: "migrations", label, action: "created" });
179
+ }
180
+ return results;
181
+ }
182
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/reconcilers/migrations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import { diffSubset } from "../diff.js";
2
+ import { sanitizeForWrite } from "../sanitize.js";
3
+ import { resolveFlowSyncIdToServerId, resolveOpSyncIdToServerId, } from "../identity.js";
4
+ async function listServerOps(client) {
5
+ const raw = await client.get("/operations?limit=-1");
6
+ if (raw === null)
7
+ return [];
8
+ if (Array.isArray(raw))
9
+ return raw;
10
+ const data = raw.data;
11
+ return Array.isArray(data) ? data : [];
12
+ }
13
+ function indexByFlowAndKey(rows) {
14
+ const map = new Map();
15
+ for (const r of rows) {
16
+ const flow = String(r.flow ?? "");
17
+ const key = String(r.key ?? "");
18
+ if (flow && key)
19
+ map.set(`${flow}::${key}`, r);
20
+ }
21
+ return map;
22
+ }
23
+ export async function reconcileOperationsPass1(input) {
24
+ const results = [];
25
+ const serverByCompositeKey = indexByFlowAndKey(await listServerOps(input.client));
26
+ for (const desired of input.operations) {
27
+ const opKey = String(desired.key ?? "");
28
+ const flowSyncId = String(desired.flow ?? "");
29
+ if (!opKey || !flowSyncId)
30
+ continue;
31
+ const label = `operations/${flowSyncId.slice(0, 8)}…/${opKey}`;
32
+ const serverFlowId = resolveFlowSyncIdToServerId(flowSyncId, input.identity);
33
+ if (!serverFlowId) {
34
+ results.push({
35
+ kind: "operations",
36
+ label,
37
+ action: "failed",
38
+ reason: `unresolved flow _syncId '${flowSyncId}' — apply flows first`,
39
+ });
40
+ continue;
41
+ }
42
+ const payload = {
43
+ ...sanitizeForWrite(desired),
44
+ flow: serverFlowId,
45
+ // First pass: null out the intra-flow refs; Pass 2 will PATCH them.
46
+ resolve: null,
47
+ reject: null,
48
+ };
49
+ const existing = serverByCompositeKey.get(`${serverFlowId}::${opKey}`) ?? null;
50
+ if (existing === null) {
51
+ if (!input.opts.dryRun) {
52
+ try {
53
+ await input.client.post("/operations", payload);
54
+ }
55
+ catch (e) {
56
+ results.push({
57
+ kind: "operations",
58
+ label,
59
+ action: "failed",
60
+ reason: e.message,
61
+ });
62
+ continue;
63
+ }
64
+ }
65
+ results.push({ kind: "operations", label, action: "created" });
66
+ continue;
67
+ }
68
+ // Skip resolve/reject in diff — Pass 2 handles those.
69
+ const { resolve: _dr, reject: _dj, ...desiredForDiff } = payload;
70
+ const { resolve: _er, reject: _ej, ...existingForDiff } = existing;
71
+ if (diffSubset(desiredForDiff, existingForDiff)) {
72
+ const id = String(existing.id ?? "");
73
+ if (!input.opts.dryRun) {
74
+ try {
75
+ await input.client.patch(`/operations/${id}`, desiredForDiff);
76
+ }
77
+ catch (e) {
78
+ results.push({
79
+ kind: "operations",
80
+ label,
81
+ action: "failed",
82
+ reason: e.message,
83
+ });
84
+ continue;
85
+ }
86
+ }
87
+ results.push({ kind: "operations", label, action: "updated" });
88
+ }
89
+ else {
90
+ results.push({ kind: "operations", label, action: "unchanged" });
91
+ }
92
+ }
93
+ return results;
94
+ }
95
+ export async function reconcileOperationsPass2(input) {
96
+ const results = [];
97
+ const serverByCompositeKey = indexByFlowAndKey(await listServerOps(input.client));
98
+ for (const desired of input.operations) {
99
+ const opKey = String(desired.key ?? "");
100
+ const flowSyncId = String(desired.flow ?? "");
101
+ if (!opKey || !flowSyncId)
102
+ continue;
103
+ const resolveSync = desired.resolve;
104
+ const rejectSync = desired.reject;
105
+ if (resolveSync == null && rejectSync == null)
106
+ continue; // nothing to link
107
+ const label = `operations/${flowSyncId.slice(0, 8)}…/${opKey} (link refs)`;
108
+ const serverFlowId = resolveFlowSyncIdToServerId(flowSyncId, input.identity);
109
+ if (!serverFlowId)
110
+ continue; // Pass 1 already flagged this
111
+ const existing = serverByCompositeKey.get(`${serverFlowId}::${opKey}`);
112
+ if (!existing)
113
+ continue; // Pass 1 already flagged
114
+ const resolveTarget = resolveSync
115
+ ? resolveOpSyncIdToServerId(String(resolveSync), input.identity)
116
+ : null;
117
+ const rejectTarget = rejectSync
118
+ ? resolveOpSyncIdToServerId(String(rejectSync), input.identity)
119
+ : null;
120
+ if (resolveSync && !resolveTarget) {
121
+ results.push({
122
+ kind: "operations",
123
+ label,
124
+ action: "failed",
125
+ reason: `unresolved resolve _syncId '${resolveSync}'`,
126
+ });
127
+ continue;
128
+ }
129
+ if (rejectSync && !rejectTarget) {
130
+ results.push({
131
+ kind: "operations",
132
+ label,
133
+ action: "failed",
134
+ reason: `unresolved reject _syncId '${rejectSync}'`,
135
+ });
136
+ continue;
137
+ }
138
+ const currentResolve = existing.resolve ?? null;
139
+ const currentReject = existing.reject ?? null;
140
+ if (currentResolve === resolveTarget && currentReject === rejectTarget)
141
+ continue;
142
+ if (!input.opts.dryRun) {
143
+ const id = String(existing.id ?? "");
144
+ try {
145
+ await input.client.patch(`/operations/${id}`, {
146
+ resolve: resolveTarget,
147
+ reject: rejectTarget,
148
+ });
149
+ }
150
+ catch (e) {
151
+ results.push({
152
+ kind: "operations",
153
+ label,
154
+ action: "failed",
155
+ reason: e.message,
156
+ });
157
+ continue;
158
+ }
159
+ }
160
+ results.push({ kind: "operations", label, action: "updated" });
161
+ }
162
+ return results;
163
+ }
164
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/reconcilers/operations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { diffSubset } from "../diff.js";
2
+ import { sanitizeForWrite } from "../sanitize.js";
3
+ import { resolvePolicySyncIdToServerId } from "../identity.js";
4
+ async function listServerPermissions(client) {
5
+ const raw = await client.get("/permissions?limit=-1");
6
+ if (raw === null)
7
+ return [];
8
+ if (Array.isArray(raw))
9
+ return raw;
10
+ const data = raw.data;
11
+ return Array.isArray(data) ? data : [];
12
+ }
13
+ function compositeKey(collection, action, policyId) {
14
+ return `${collection}::${action}::${policyId}`;
15
+ }
16
+ export async function reconcilePermissions(input) {
17
+ const results = [];
18
+ const server = await listServerPermissions(input.client);
19
+ const byKey = new Map();
20
+ for (const row of server) {
21
+ const c = String(row.collection ?? "");
22
+ const a = String(row.action ?? "");
23
+ const p = String(row.policy ?? "");
24
+ byKey.set(compositeKey(c, a, p), row);
25
+ }
26
+ for (const desired of input.permissions) {
27
+ const collection = String(desired.collection ?? "");
28
+ const action = String(desired.action ?? "");
29
+ const policySync = String(desired.policy ?? "");
30
+ if (!collection || !action || !policySync)
31
+ continue;
32
+ const policyServerId = resolvePolicySyncIdToServerId(policySync, input.identity);
33
+ const label = `permissions/${collection}.${action}(${policySync.slice(0, 8)}…)`;
34
+ if (policyServerId === null) {
35
+ results.push({
36
+ kind: "permissions",
37
+ label,
38
+ action: "failed",
39
+ reason: `unresolved policy _syncId '${policySync}' — apply policies first`,
40
+ });
41
+ continue;
42
+ }
43
+ const payload = sanitizeForWrite(desired);
44
+ payload.policy = policyServerId;
45
+ const existing = byKey.get(compositeKey(collection, action, policyServerId)) ?? null;
46
+ if (existing === null) {
47
+ if (!input.opts.dryRun) {
48
+ try {
49
+ await input.client.post("/permissions", payload);
50
+ }
51
+ catch (e) {
52
+ results.push({
53
+ kind: "permissions",
54
+ label,
55
+ action: "failed",
56
+ reason: e.message,
57
+ });
58
+ continue;
59
+ }
60
+ }
61
+ results.push({ kind: "permissions", label, action: "created" });
62
+ }
63
+ else if (diffSubset(payload, existing)) {
64
+ const id = String(existing.id ?? "");
65
+ if (!input.opts.dryRun) {
66
+ try {
67
+ await input.client.patch(`/permissions/${id}`, payload);
68
+ }
69
+ catch (e) {
70
+ results.push({
71
+ kind: "permissions",
72
+ label,
73
+ action: "failed",
74
+ reason: e.message,
75
+ });
76
+ continue;
77
+ }
78
+ }
79
+ results.push({ kind: "permissions", label, action: "updated" });
80
+ }
81
+ else {
82
+ results.push({ kind: "permissions", label, action: "unchanged" });
83
+ }
84
+ }
85
+ return results;
86
+ }
87
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/reconcilers/permissions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ import { diffSubset } from "../diff.js";
2
+ import { sanitizeForWrite } from "../sanitize.js";
3
+ import { resolveRoleSyncIdToServerId } from "../identity.js";
4
+ async function listServerPolicies(client) {
5
+ const raw = await client.get("/policies?limit=-1");
6
+ if (raw === null)
7
+ return [];
8
+ if (Array.isArray(raw))
9
+ return raw;
10
+ const data = raw.data;
11
+ return Array.isArray(data) ? data : [];
12
+ }
13
+ function indexByName(rows) {
14
+ const map = new Map();
15
+ for (const r of rows) {
16
+ const n = String(r.name ?? "");
17
+ if (n)
18
+ map.set(n, r);
19
+ }
20
+ return map;
21
+ }
22
+ function resolveRolesFks(payload, identity, unresolved) {
23
+ const roles = payload.roles;
24
+ if (!Array.isArray(roles))
25
+ return payload;
26
+ const resolved = roles.map((entry) => {
27
+ if (entry === null || typeof entry !== "object")
28
+ return entry;
29
+ const syncId = String(entry.role ?? "");
30
+ if (!syncId)
31
+ return entry;
32
+ const realId = resolveRoleSyncIdToServerId(syncId, identity);
33
+ if (realId === null) {
34
+ unresolved.push(syncId);
35
+ return entry;
36
+ }
37
+ return { ...entry, role: realId };
38
+ });
39
+ return { ...payload, roles: resolved };
40
+ }
41
+ export async function reconcilePolicies(input) {
42
+ const results = [];
43
+ const serverByName = indexByName(await listServerPolicies(input.client));
44
+ for (const desired of input.policies) {
45
+ const name = String(desired.name ?? "");
46
+ if (!name)
47
+ continue;
48
+ const label = `policies/${name}`;
49
+ const existing = serverByName.get(name) ?? null;
50
+ const unresolved = [];
51
+ const payload = resolveRolesFks(sanitizeForWrite(desired), input.identity, unresolved);
52
+ if (unresolved.length) {
53
+ results.push({
54
+ kind: "policies",
55
+ label,
56
+ action: "failed",
57
+ reason: `unresolved role _syncId(s): ${unresolved.join(", ")}`,
58
+ });
59
+ continue;
60
+ }
61
+ if (existing === null) {
62
+ if (!input.opts.dryRun) {
63
+ try {
64
+ await input.client.post("/policies", payload);
65
+ }
66
+ catch (e) {
67
+ results.push({ kind: "policies", label, action: "failed", reason: e.message });
68
+ continue;
69
+ }
70
+ }
71
+ results.push({ kind: "policies", label, action: "created" });
72
+ continue;
73
+ }
74
+ // Directus stores policy↔role as an M2M junction and reports `roles` on
75
+ // GET as junction-row ids, but expects nested {role, sort, user} objects
76
+ // on POST/PATCH. Shape mismatch → false-positive drift. Also, roles
77
+ // assignments often drift server-side (users assigning roles ad-hoc);
78
+ // ignoring `roles` in the update path keeps this reconciler focused on
79
+ // the policy row itself. Junction sync is a future concern.
80
+ const stripRoles = (obj) => {
81
+ const { roles: _r, users: _u, permissions: _p, ...rest } = obj;
82
+ return rest;
83
+ };
84
+ const desiredCmp = stripRoles(payload);
85
+ const existingCmp = stripRoles(existing);
86
+ if (diffSubset(desiredCmp, existingCmp)) {
87
+ const id = String(existing.id ?? "");
88
+ if (!input.opts.dryRun) {
89
+ try {
90
+ await input.client.patch(`/policies/${id}`, desiredCmp);
91
+ }
92
+ catch (e) {
93
+ results.push({ kind: "policies", label, action: "failed", reason: e.message });
94
+ continue;
95
+ }
96
+ }
97
+ results.push({ kind: "policies", label, action: "updated" });
98
+ }
99
+ else {
100
+ results.push({ kind: "policies", label, action: "unchanged" });
101
+ }
102
+ }
103
+ return results;
104
+ }
105
+ //# sourceMappingURL=policies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.js","sourceRoot":"","sources":["../../src/reconcilers/policies.ts"],"names":[],"mappings":"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"}