@myna-sh/cli 0.8.0 → 0.9.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/dist/main.js +252 -21
- package/dist/main.js.map +1 -1
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -130,10 +130,10 @@ var HttpClient = class {
|
|
|
130
130
|
* while in flight — this is request coalescing, not a cache.
|
|
131
131
|
*/
|
|
132
132
|
inFlight = /* @__PURE__ */ new Map();
|
|
133
|
-
dedupe(key,
|
|
133
|
+
dedupe(key, run2) {
|
|
134
134
|
const existing = this.inFlight.get(key);
|
|
135
135
|
if (existing) return existing.then((response) => response.clone());
|
|
136
|
-
const started =
|
|
136
|
+
const started = run2();
|
|
137
137
|
this.inFlight.set(key, started);
|
|
138
138
|
const cleanup = () => {
|
|
139
139
|
if (this.inFlight.get(key) === started) this.inFlight.delete(key);
|
|
@@ -468,6 +468,16 @@ var ManagementClient = class {
|
|
|
468
468
|
collection ? { origin, collection } : { origin },
|
|
469
469
|
signal
|
|
470
470
|
),
|
|
471
|
+
/**
|
|
472
|
+
* Why a call into this project would be denied: whether the credential
|
|
473
|
+
* holds a capability, whether it is confined to other collections, and
|
|
474
|
+
* whether the collection exists at all.
|
|
475
|
+
*/
|
|
476
|
+
access: (project, opts = {}, signal) => this.get(
|
|
477
|
+
`/projects/${enc(project)}/access`,
|
|
478
|
+
{ capability: opts.capability, collection: opts.collection },
|
|
479
|
+
signal
|
|
480
|
+
),
|
|
471
481
|
views: (project, signal) => this.get(`/projects/${enc(project)}/views`, void 0, signal),
|
|
472
482
|
createView: (project, body) => this.mutate("POST", `/projects/${enc(project)}/views`, body),
|
|
473
483
|
deleteView: (project, view) => this.mutate("DELETE", `/projects/${enc(project)}/views/${enc(view)}`)
|
|
@@ -649,6 +659,19 @@ var ManagementClient = class {
|
|
|
649
659
|
}),
|
|
650
660
|
get: (project, asset, signal) => this.get(`/projects/${enc(project)}/assets/${enc(asset)}`, void 0, signal),
|
|
651
661
|
usage: (project, asset, signal) => this.get(`/projects/${enc(project)}/assets/${enc(asset)}`, { usage: "true" }, signal),
|
|
662
|
+
/**
|
|
663
|
+
* Assets that look like the same picture, by perceptual hash — a resized or
|
|
664
|
+
* re-encoded copy, which `checksum` cannot see. `maxDistance` is in bits out
|
|
665
|
+
* of 64 (default 5, maximum 16).
|
|
666
|
+
*/
|
|
667
|
+
similar: (project, asset, opts = {}, signal) => this.get(
|
|
668
|
+
`/projects/${enc(project)}/assets/${enc(asset)}/similar`,
|
|
669
|
+
{
|
|
670
|
+
maxDistance: opts.maxDistance === void 0 ? void 0 : String(opts.maxDistance),
|
|
671
|
+
limit: opts.limit === void 0 ? void 0 : String(opts.limit)
|
|
672
|
+
},
|
|
673
|
+
signal
|
|
674
|
+
).then((r) => r.similar),
|
|
652
675
|
update: (project, asset, body) => this.mutate("PATCH", `/projects/${enc(project)}/assets/${enc(asset)}`, body),
|
|
653
676
|
replace: (project, asset, body) => this.mutate("POST", `/projects/${enc(project)}/assets/${enc(asset)}/replace`, body),
|
|
654
677
|
delete: (project, asset) => this.mutate("DELETE", `/projects/${enc(project)}/assets/${enc(asset)}`),
|
|
@@ -1194,7 +1217,33 @@ function startDevice(apiUrl) {
|
|
|
1194
1217
|
function pollDevice(apiUrl, deviceCode) {
|
|
1195
1218
|
return apiPost(apiUrl, `/auth/device/${deviceCode}/token`, { deviceCode });
|
|
1196
1219
|
}
|
|
1220
|
+
function registerAccess(program) {
|
|
1221
|
+
program.command("access").description("Explain what this credential may do in a project, and why a call was denied").option("--capability <cap>", "capability to check, e.g. content:publish").option("--collection <key>", "collection the denied call touched").action(
|
|
1222
|
+
handle(async (ctx, _args, opts) => {
|
|
1223
|
+
const project = ctx.requireProject();
|
|
1224
|
+
const access = await ctx.management().projects.access(project, {
|
|
1225
|
+
capability: opts.capability,
|
|
1226
|
+
collection: opts.collection
|
|
1227
|
+
});
|
|
1228
|
+
emit(access, () => {
|
|
1229
|
+
keyValues([
|
|
1230
|
+
["Project", access.projectSlug],
|
|
1231
|
+
["Credential", access.credential + (access.role ? ` (${access.role})` : "")],
|
|
1232
|
+
["Capabilities", access.capabilities.join(", ") || "(none)"],
|
|
1233
|
+
["Collections", access.allowedCollections?.join(", ") ?? "all"]
|
|
1234
|
+
]);
|
|
1235
|
+
for (const check2 of access.checks) {
|
|
1236
|
+
diag(` ${check2.ok ? "ok " : "fail"} ${check2.id} \u2014 ${check2.detail}`);
|
|
1237
|
+
}
|
|
1238
|
+
if (access.remedy) diag(`
|
|
1239
|
+
remedy: ${access.remedy}`);
|
|
1240
|
+
});
|
|
1241
|
+
if (!access.allowed) process.exitCode = 1;
|
|
1242
|
+
})
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1197
1245
|
function registerAuth(program) {
|
|
1246
|
+
registerAccess(program);
|
|
1198
1247
|
const login = program.command("login").description("Authenticate via the browser device-authorization flow").action(
|
|
1199
1248
|
handle(async (ctx) => {
|
|
1200
1249
|
const start = await startDevice(ctx.apiUrl);
|
|
@@ -1483,7 +1532,7 @@ function registerWorkspace(program) {
|
|
|
1483
1532
|
}
|
|
1484
1533
|
|
|
1485
1534
|
// src/commands/schema.ts
|
|
1486
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1535
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1487
1536
|
import { join as join4 } from "path";
|
|
1488
1537
|
|
|
1489
1538
|
// src/schema-loader.ts
|
|
@@ -1717,6 +1766,89 @@ function varName(name) {
|
|
|
1717
1766
|
return /^[A-Za-z_$]/.test(camel) ? camel : `_${camel}`;
|
|
1718
1767
|
}
|
|
1719
1768
|
|
|
1769
|
+
// src/git.ts
|
|
1770
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1771
|
+
import { createHash as createHash2 } from "crypto";
|
|
1772
|
+
function run(command, args, cwd) {
|
|
1773
|
+
try {
|
|
1774
|
+
return execFileSync2(command, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
1775
|
+
} catch (error) {
|
|
1776
|
+
const stderr = error.stderr;
|
|
1777
|
+
const detail = typeof stderr === "string" ? stderr : stderr?.toString("utf8");
|
|
1778
|
+
throw new CliError(`${command} ${args[0]} failed: ${(detail || error.message).trim()}`);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function git(args, cwd) {
|
|
1782
|
+
return run("git", args, cwd).trim();
|
|
1783
|
+
}
|
|
1784
|
+
function gitQuiet(args, cwd) {
|
|
1785
|
+
try {
|
|
1786
|
+
execFileSync2("git", args, { cwd, stdio: "ignore" });
|
|
1787
|
+
} catch {
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function available(command) {
|
|
1791
|
+
try {
|
|
1792
|
+
execFileSync2(command, ["--version"], { stdio: "ignore" });
|
|
1793
|
+
return true;
|
|
1794
|
+
} catch {
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
function branchForContent(contents) {
|
|
1799
|
+
const digest = createHash2("sha256").update(contents.join("\0")).digest("hex").slice(0, 8);
|
|
1800
|
+
return `myna/schema-${digest}`;
|
|
1801
|
+
}
|
|
1802
|
+
function openPullRequest(input) {
|
|
1803
|
+
if (!available("git")) throw new CliError("git is required to open a pull request.");
|
|
1804
|
+
if (!available("gh")) {
|
|
1805
|
+
throw new CliError("The GitHub CLI (gh) is required to open a pull request. See https://cli.github.com.");
|
|
1806
|
+
}
|
|
1807
|
+
if (git(["rev-parse", "--is-inside-work-tree"], input.cwd) !== "true") {
|
|
1808
|
+
throw new CliError(`${input.cwd} is not inside a git repository.`);
|
|
1809
|
+
}
|
|
1810
|
+
const base = input.base ?? git(["rev-parse", "--abbrev-ref", "HEAD"], input.cwd);
|
|
1811
|
+
if (base === "HEAD") {
|
|
1812
|
+
throw new CliError("HEAD is detached; pass --base to say which branch to open the pull request against.");
|
|
1813
|
+
}
|
|
1814
|
+
const exists = (() => {
|
|
1815
|
+
try {
|
|
1816
|
+
execFileSync2("git", ["rev-parse", "--verify", `refs/heads/${input.branch}`], {
|
|
1817
|
+
cwd: input.cwd,
|
|
1818
|
+
stdio: "ignore"
|
|
1819
|
+
});
|
|
1820
|
+
return true;
|
|
1821
|
+
} catch {
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
})();
|
|
1825
|
+
if (exists) return { opened: false, branch: input.branch, base, url: null, reason: "branch-exists" };
|
|
1826
|
+
git(["checkout", "-b", input.branch], input.cwd);
|
|
1827
|
+
let pushed = false;
|
|
1828
|
+
try {
|
|
1829
|
+
git(["add", "--", ...input.files], input.cwd);
|
|
1830
|
+
git(["commit", "-m", input.title, "-m", input.body], input.cwd);
|
|
1831
|
+
git(["push", "--set-upstream", "origin", input.branch], input.cwd);
|
|
1832
|
+
pushed = true;
|
|
1833
|
+
const url = run(
|
|
1834
|
+
"gh",
|
|
1835
|
+
["pr", "create", "--base", base, "--head", input.branch, "--title", input.title, "--body", input.body],
|
|
1836
|
+
input.cwd
|
|
1837
|
+
).trim().split("\n").filter((line) => line.startsWith("http")).pop();
|
|
1838
|
+
git(["checkout", base], input.cwd);
|
|
1839
|
+
return { opened: true, branch: input.branch, base, url: url ?? null };
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
gitQuiet(["checkout", base], input.cwd);
|
|
1842
|
+
if (pushed) gitQuiet(["push", "origin", "--delete", input.branch], input.cwd);
|
|
1843
|
+
gitQuiet(["branch", "-D", input.branch], input.cwd);
|
|
1844
|
+
throw error;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
function dirtyFiles(cwd, paths) {
|
|
1848
|
+
if (paths.length === 0) return [];
|
|
1849
|
+
return run("git", ["status", "--porcelain", "--", ...paths], cwd).split("\n").filter(Boolean).map((line) => line.slice(3).trim());
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1720
1852
|
// src/commands/schema.ts
|
|
1721
1853
|
async function deployedSchemas(ctx, project) {
|
|
1722
1854
|
const mgmt = ctx.management();
|
|
@@ -1762,21 +1894,59 @@ function registerSchema(program) {
|
|
|
1762
1894
|
});
|
|
1763
1895
|
})
|
|
1764
1896
|
);
|
|
1765
|
-
schema.command("pull").description("Write deployed schemas to local DSL files").option("--schema-dir <dir>", "target schema directory").action(
|
|
1897
|
+
schema.command("pull").description("Write deployed schemas to local DSL files").option("--schema-dir <dir>", "target schema directory").option("--open-pr", "commit the drift onto a branch and open a pull request instead of writing in place", false).option("--branch <name>", "branch to use with --open-pr (default: derived from the content)").option("--base <branch>", "branch to open the pull request against (default: the current branch)").action(
|
|
1766
1898
|
handle(async (ctx, _args, opts) => {
|
|
1767
1899
|
const project = ctx.requireProject();
|
|
1768
1900
|
const dir = schemaDirFor(ctx.linkedRoot, opts.schemaDir);
|
|
1769
1901
|
const schemas = await deployedSchemas(ctx, project);
|
|
1770
|
-
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1902
|
+
const rendered = schemas.map((s) => ({ file: join4(dir, `${s.name}.ts`), code: schemaToDsl(s) }));
|
|
1903
|
+
const changed = rendered.filter(
|
|
1904
|
+
(r) => !existsSync4(r.file) || readFileSync3(r.file, "utf8") !== r.code
|
|
1905
|
+
);
|
|
1906
|
+
const write = () => {
|
|
1907
|
+
mkdirSync3(dir, { recursive: true });
|
|
1908
|
+
for (const r of rendered) writeFileSync3(r.file, r.code);
|
|
1909
|
+
return rendered.map((r) => r.file);
|
|
1910
|
+
};
|
|
1911
|
+
if (!opts.openPr) {
|
|
1912
|
+
const written = write();
|
|
1913
|
+
emit({ pulled: schemas.length, files: written, changed: changed.map((c) => c.file) }, () => {
|
|
1914
|
+
for (const f of written) process.stdout.write(` wrote ${f}
|
|
1779
1915
|
`);
|
|
1916
|
+
});
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
if (changed.length === 0) {
|
|
1920
|
+
emit(
|
|
1921
|
+
{ pulled: schemas.length, changed: [], pullRequest: null },
|
|
1922
|
+
() => diag("No drift: the local schema files already match the deployed schema.")
|
|
1923
|
+
);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
const root = ctx.linkedRoot ?? process.cwd();
|
|
1927
|
+
const dirty = dirtyFiles(root, [dir]);
|
|
1928
|
+
if (dirty.length > 0) {
|
|
1929
|
+
throw new UsageError(
|
|
1930
|
+
`Commit or stash changes under ${dir} first: ${dirty.join(", ")}.`
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
const body = await driftBody(ctx, project, dir, changed.map((c) => c.file));
|
|
1934
|
+
write();
|
|
1935
|
+
const branch = opts.branch ?? branchForContent(changed.map((c) => c.code));
|
|
1936
|
+
const result = openPullRequest({
|
|
1937
|
+
cwd: root,
|
|
1938
|
+
files: changed.map((c) => c.file),
|
|
1939
|
+
branch,
|
|
1940
|
+
base: opts.base,
|
|
1941
|
+
title: `chore(schema): sync ${changed.length} collection(s) from ${project}`,
|
|
1942
|
+
body
|
|
1943
|
+
});
|
|
1944
|
+
emit({ pulled: schemas.length, changed: changed.map((c) => c.file), pullRequest: result }, () => {
|
|
1945
|
+
if (!result.opened) {
|
|
1946
|
+
diag(`Branch ${result.branch} already exists \u2014 this drift is already proposed.`);
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
diag(`Opened ${result.url ?? `pull request from ${result.branch}`} against ${result.base}.`);
|
|
1780
1950
|
});
|
|
1781
1951
|
})
|
|
1782
1952
|
);
|
|
@@ -1894,6 +2064,29 @@ function registerSchema(program) {
|
|
|
1894
2064
|
})
|
|
1895
2065
|
);
|
|
1896
2066
|
}
|
|
2067
|
+
async function driftBody(ctx, project, dir, files) {
|
|
2068
|
+
const lines = [
|
|
2069
|
+
`Deployed schema for \`${project}\`, pulled into this repository by \`myna schema pull --open-pr\`.`,
|
|
2070
|
+
"",
|
|
2071
|
+
"Files:",
|
|
2072
|
+
...files.map((f) => `- \`${f}\``)
|
|
2073
|
+
];
|
|
2074
|
+
try {
|
|
2075
|
+
const local = await loadLocalSchemas(dir);
|
|
2076
|
+
const diff = await ctx.management().schema.diff(project, canonicalJson(local));
|
|
2077
|
+
if (diff.ops.length > 0) {
|
|
2078
|
+
lines.push(
|
|
2079
|
+
"",
|
|
2080
|
+
`Before this change, pushing this repository would have applied ${diff.ops.length} operation(s) (${diff.classification}):`,
|
|
2081
|
+
"",
|
|
2082
|
+
...diff.ops.map((op) => `- \`${op.kind}\` ${op.collection}${op.field ? `.${op.field}` : ""} \u2014 ${op.detail}`)
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
} catch {
|
|
2086
|
+
lines.push("", "_The local schema could not be diffed; review the file changes directly._");
|
|
2087
|
+
}
|
|
2088
|
+
return lines.join("\n");
|
|
2089
|
+
}
|
|
1897
2090
|
function renderDiff(diff) {
|
|
1898
2091
|
if (diff.ops.length === 0) {
|
|
1899
2092
|
diag("No changes.");
|
|
@@ -2482,6 +2675,9 @@ function registerChanges(program) {
|
|
|
2482
2675
|
{ header: "BEFORE", value: (c) => preview(c.before) },
|
|
2483
2676
|
{ header: "AFTER", value: (c) => preview(c.after) }
|
|
2484
2677
|
]);
|
|
2678
|
+
for (const change of item.changes) {
|
|
2679
|
+
if (change.segments) diag(` ${change.path}: ${inlineWords(change.segments)}`);
|
|
2680
|
+
}
|
|
2485
2681
|
}
|
|
2486
2682
|
if (diff.items.length === 0) diag("No items.");
|
|
2487
2683
|
});
|
|
@@ -2563,6 +2759,15 @@ function registerChanges(program) {
|
|
|
2563
2759
|
})
|
|
2564
2760
|
);
|
|
2565
2761
|
}
|
|
2762
|
+
function inlineWords(segments) {
|
|
2763
|
+
const KEEP = 32;
|
|
2764
|
+
return segments.map((s) => {
|
|
2765
|
+
const flat = s.value.replaceAll(/\s+/g, " ");
|
|
2766
|
+
if (s.op === "insert") return `{+${flat}+}`;
|
|
2767
|
+
if (s.op === "delete") return `[-${flat}-]`;
|
|
2768
|
+
return flat.length > KEEP * 2 ? `${flat.slice(0, KEEP)} \u2026 ${flat.slice(-KEEP)}` : flat;
|
|
2769
|
+
}).join("");
|
|
2770
|
+
}
|
|
2566
2771
|
function preview(value) {
|
|
2567
2772
|
if (value === void 0 || value === null) return "\u2014";
|
|
2568
2773
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
@@ -2703,6 +2908,10 @@ function registerAssets(program) {
|
|
|
2703
2908
|
let asset = await ctx.management().assets.upload(project, path);
|
|
2704
2909
|
if (opts.alt) asset = await ctx.management().assets.update(project, asset.id, { defaultAlt: opts.alt });
|
|
2705
2910
|
uploaded.push(asset);
|
|
2911
|
+
const similar = await ctx.management().assets.similar(project, asset.id).catch(() => []);
|
|
2912
|
+
for (const match of similar) {
|
|
2913
|
+
diag(` looks like ${match.asset.id} (${match.asset.displayFilename}), distance ${match.distance}`);
|
|
2914
|
+
}
|
|
2706
2915
|
}
|
|
2707
2916
|
emit(
|
|
2708
2917
|
uploaded.length === 1 ? uploaded[0] : uploaded,
|
|
@@ -2734,6 +2943,26 @@ function registerAssets(program) {
|
|
|
2734
2943
|
);
|
|
2735
2944
|
})
|
|
2736
2945
|
);
|
|
2946
|
+
assets.command("similar").description("Find images that look like the same picture as this one").argument("<id>", "asset id (ast_)").option("--max-distance <n>", "how many of the fingerprint's 64 bits may differ", "5").action(
|
|
2947
|
+
handle(async (ctx, args, opts) => {
|
|
2948
|
+
const project = ctx.requireProject();
|
|
2949
|
+
const similar = await ctx.management().assets.similar(project, args[0], {
|
|
2950
|
+
maxDistance: Number(opts.maxDistance)
|
|
2951
|
+
});
|
|
2952
|
+
emit(similar, () => {
|
|
2953
|
+
if (similar.length === 0) {
|
|
2954
|
+
diag("No near-duplicates. (Images uploaded before fingerprinting shipped have none.)");
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
table(similar, [
|
|
2958
|
+
{ header: "ID", value: (s) => s.asset.id },
|
|
2959
|
+
{ header: "FILENAME", value: (s) => s.asset.displayFilename },
|
|
2960
|
+
{ header: "DISTANCE", value: (s) => String(s.distance) },
|
|
2961
|
+
{ header: "STATE", value: (s) => s.asset.state }
|
|
2962
|
+
]);
|
|
2963
|
+
});
|
|
2964
|
+
})
|
|
2965
|
+
);
|
|
2737
2966
|
assets.command("inspect").description("Show an asset and its usage").argument("<id>", "asset id (ast_)").action(
|
|
2738
2967
|
handle(async (ctx, args) => {
|
|
2739
2968
|
const project = ctx.requireProject();
|
|
@@ -3459,11 +3688,11 @@ function registerBilling(program) {
|
|
|
3459
3688
|
}
|
|
3460
3689
|
|
|
3461
3690
|
// src/commands/doctor.ts
|
|
3462
|
-
import { existsSync as
|
|
3691
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
3463
3692
|
import { join as join6 } from "path";
|
|
3464
3693
|
|
|
3465
3694
|
// src/version.ts
|
|
3466
|
-
var VERSION = true ? "0.
|
|
3695
|
+
var VERSION = true ? "0.9.0" : "0.0.0-dev";
|
|
3467
3696
|
|
|
3468
3697
|
// src/commands/doctor.ts
|
|
3469
3698
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
@@ -3650,7 +3879,7 @@ async function checkOrigin(ctx, origin) {
|
|
|
3650
3879
|
}
|
|
3651
3880
|
async function checkSchema(ctx, schemaDir) {
|
|
3652
3881
|
const dir = schemaDirFor(ctx.linkedRoot, schemaDir);
|
|
3653
|
-
if (!
|
|
3882
|
+
if (!existsSync5(dir)) {
|
|
3654
3883
|
return check("schema.drift", "Local schema", "skip", `No schema directory at ${dir}.`);
|
|
3655
3884
|
}
|
|
3656
3885
|
if (!ctx.project) {
|
|
@@ -3677,7 +3906,9 @@ async function checkSchema(ctx, schemaDir) {
|
|
|
3677
3906
|
"Local schema",
|
|
3678
3907
|
"warn",
|
|
3679
3908
|
`${diff.ops.length} undeployed change(s) (${diff.classification}).`,
|
|
3680
|
-
|
|
3909
|
+
// Drift has two directions and two fixes: push local ahead, or pull
|
|
3910
|
+
// the deployed schema back into the repository as a reviewable change.
|
|
3911
|
+
"myna schema diff, then myna schema push \u2014 or myna schema pull --open-pr if the deployed schema is the one to keep"
|
|
3681
3912
|
);
|
|
3682
3913
|
} catch (error) {
|
|
3683
3914
|
return check(
|
|
@@ -3711,7 +3942,7 @@ function findGeneratedTypes(root, depth = 4) {
|
|
|
3711
3942
|
}
|
|
3712
3943
|
if (!/\.(ts|d\.ts)$/.test(entry)) continue;
|
|
3713
3944
|
try {
|
|
3714
|
-
if (looksGenerated(
|
|
3945
|
+
if (looksGenerated(readFileSync4(full, "utf8"))) return full;
|
|
3715
3946
|
} catch {
|
|
3716
3947
|
}
|
|
3717
3948
|
}
|
|
@@ -3728,16 +3959,16 @@ async function checkTypes(ctx, explicit, schemaDir) {
|
|
|
3728
3959
|
if (!file) {
|
|
3729
3960
|
return check("types.freshness", "Generated types", "skip", "No generated types file found.");
|
|
3730
3961
|
}
|
|
3731
|
-
if (!
|
|
3962
|
+
if (!existsSync5(file)) {
|
|
3732
3963
|
return check("types.freshness", "Generated types", "fail", `${file} does not exist.`);
|
|
3733
3964
|
}
|
|
3734
3965
|
const dir = schemaDirFor(ctx.linkedRoot, schemaDir);
|
|
3735
|
-
if (!
|
|
3966
|
+
if (!existsSync5(dir)) {
|
|
3736
3967
|
return check("types.freshness", "Generated types", "skip", `Found ${file} but no schema directory to compare against.`);
|
|
3737
3968
|
}
|
|
3738
3969
|
try {
|
|
3739
3970
|
const expected = generateTypesModule(await loadLocalSchemas(dir));
|
|
3740
|
-
return
|
|
3971
|
+
return readFileSync4(file, "utf8") === expected ? check("types.freshness", "Generated types", "pass", `${file} matches the local schema.`) : check(
|
|
3741
3972
|
"types.freshness",
|
|
3742
3973
|
"Generated types",
|
|
3743
3974
|
"warn",
|