@myna-sh/cli 0.11.0 → 0.12.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 +24 -0
- package/dist/main.js +295 -15
- package/dist/main.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -64,6 +64,30 @@ myna changes diff chs_...
|
|
|
64
64
|
|
|
65
65
|
Run `myna --help` or `myna <command> --help` for the complete command reference. Global flags include `--json`, `--organization`, `--project`, `--token`, and `--api-url`.
|
|
66
66
|
|
|
67
|
+
## Pin content to a release
|
|
68
|
+
|
|
69
|
+
`myna.lock` records the release a repository builds against, so the same commit
|
|
70
|
+
builds the same site. `myna pull` moves it.
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
myna pull # pin to the newest release
|
|
74
|
+
myna pull --release 42 # pin to a specific one
|
|
75
|
+
myna pull --check # non-zero exit when the pin is behind (for CI)
|
|
76
|
+
myna pull --open-pr # bump on a branch and open a pull request
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`--open-pr` writes a pull request whose body is the field-level diff of every
|
|
80
|
+
release being taken on, so CI runs the real build against the new content before
|
|
81
|
+
anyone merges it. It needs `git` and the GitHub CLI. Read the pin with
|
|
82
|
+
`readLock()` from `@myna-sh/sdk/lock` and pass it to `createMyna({ release })`.
|
|
83
|
+
|
|
84
|
+
Add `--run` when the repository generates something from content, and the
|
|
85
|
+
regenerated artifact is committed with the pin that produced it:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
myna pull --open-pr --run 'pnpm build:content'
|
|
89
|
+
```
|
|
90
|
+
|
|
67
91
|
## Environment
|
|
68
92
|
|
|
69
93
|
```text
|
package/dist/main.js
CHANGED
|
@@ -1533,7 +1533,7 @@ function registerWorkspace(program) {
|
|
|
1533
1533
|
|
|
1534
1534
|
// src/commands/schema.ts
|
|
1535
1535
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1536
|
-
import { join as
|
|
1536
|
+
import { join as join5 } from "path";
|
|
1537
1537
|
|
|
1538
1538
|
// src/schema-loader.ts
|
|
1539
1539
|
import { existsSync as existsSync3, readdirSync, statSync } from "fs";
|
|
@@ -1769,6 +1769,8 @@ function varName(name) {
|
|
|
1769
1769
|
// src/git.ts
|
|
1770
1770
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1771
1771
|
import { createHash as createHash2 } from "crypto";
|
|
1772
|
+
import { rmSync as rmSync2 } from "fs";
|
|
1773
|
+
import { join as join4 } from "path";
|
|
1772
1774
|
function run(command, args, cwd) {
|
|
1773
1775
|
try {
|
|
1774
1776
|
return execFileSync2(command, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -1799,6 +1801,10 @@ function branchForContent(contents) {
|
|
|
1799
1801
|
const digest = createHash2("sha256").update(contents.join("\0")).digest("hex").slice(0, 8);
|
|
1800
1802
|
return `myna/schema-${digest}`;
|
|
1801
1803
|
}
|
|
1804
|
+
function branchForRelease(project, release) {
|
|
1805
|
+
const slug = project.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "project";
|
|
1806
|
+
return `myna/content-${slug}-r${release}`;
|
|
1807
|
+
}
|
|
1802
1808
|
function openPullRequest(input) {
|
|
1803
1809
|
if (!available("git")) throw new CliError("git is required to open a pull request.");
|
|
1804
1810
|
if (!available("gh")) {
|
|
@@ -1848,6 +1854,22 @@ function dirtyFiles(cwd, paths) {
|
|
|
1848
1854
|
if (paths.length === 0) return [];
|
|
1849
1855
|
return run("git", ["status", "--porcelain", "--", ...paths], cwd).split("\n").filter(Boolean).map((line) => line.slice(3).trim());
|
|
1850
1856
|
}
|
|
1857
|
+
function repositoryRoot(cwd) {
|
|
1858
|
+
return git(["rev-parse", "--show-toplevel"], cwd);
|
|
1859
|
+
}
|
|
1860
|
+
function worktreeChanges(cwd) {
|
|
1861
|
+
const root = repositoryRoot(cwd);
|
|
1862
|
+
return run("git", ["status", "--porcelain"], cwd).split("\n").filter(Boolean).map((line) => ({
|
|
1863
|
+
file: join4(root, line.slice(3).trim()),
|
|
1864
|
+
untracked: line.startsWith("??")
|
|
1865
|
+
}));
|
|
1866
|
+
}
|
|
1867
|
+
function discardChanges(cwd, changes) {
|
|
1868
|
+
for (const change of changes) {
|
|
1869
|
+
if (change.untracked) rmSync2(change.file, { force: true, recursive: true });
|
|
1870
|
+
else gitQuiet(["checkout", "--", change.file], cwd);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1851
1873
|
|
|
1852
1874
|
// src/commands/schema.ts
|
|
1853
1875
|
async function deployedSchemas(ctx, project) {
|
|
@@ -1899,7 +1921,7 @@ function registerSchema(program) {
|
|
|
1899
1921
|
const project = ctx.requireProject();
|
|
1900
1922
|
const dir = schemaDirFor(ctx.linkedRoot, opts.schemaDir);
|
|
1901
1923
|
const schemas = await deployedSchemas(ctx, project);
|
|
1902
|
-
const rendered = schemas.map((s) => ({ file:
|
|
1924
|
+
const rendered = schemas.map((s) => ({ file: join5(dir, `${s.name}.ts`), code: schemaToDsl(s) }));
|
|
1903
1925
|
const changed = rendered.filter(
|
|
1904
1926
|
(r) => !existsSync4(r.file) || readFileSync3(r.file, "utf8") !== r.code
|
|
1905
1927
|
);
|
|
@@ -2108,7 +2130,7 @@ function renderDiff(diff) {
|
|
|
2108
2130
|
|
|
2109
2131
|
// src/content-files.ts
|
|
2110
2132
|
import { readFile as readFile2, readdir, stat } from "fs/promises";
|
|
2111
|
-
import { basename as basename2, extname, join as
|
|
2133
|
+
import { basename as basename2, extname, join as join6 } from "path";
|
|
2112
2134
|
async function detectFormat(path) {
|
|
2113
2135
|
const info = await stat(path).catch(() => void 0);
|
|
2114
2136
|
if (!info) throw new UsageError(`No such file or directory: ${path}`);
|
|
@@ -2186,7 +2208,7 @@ async function readJsonRows(file) {
|
|
|
2186
2208
|
}
|
|
2187
2209
|
async function readMarkdownRows(dir, bodyField) {
|
|
2188
2210
|
const info = await stat(dir).catch(() => void 0);
|
|
2189
|
-
const files = info?.isDirectory() ? (await readdir(dir)).filter((name) => [".md", ".markdown"].includes(extname(name).toLowerCase())).sort().map((name) =>
|
|
2211
|
+
const files = info?.isDirectory() ? (await readdir(dir)).filter((name) => [".md", ".markdown"].includes(extname(name).toLowerCase())).sort().map((name) => join6(dir, name)) : [dir];
|
|
2190
2212
|
if (files.length === 0) throw new UsageError(`No .md files found in ${dir}`);
|
|
2191
2213
|
const rows = [];
|
|
2192
2214
|
for (const file of files) {
|
|
@@ -2823,6 +2845,25 @@ function registerReleases(program) {
|
|
|
2823
2845
|
});
|
|
2824
2846
|
})
|
|
2825
2847
|
);
|
|
2848
|
+
releases.command("diff").description("Show what a release changed, field by field").argument("<release>", "release number or published change set id").action(
|
|
2849
|
+
handle(async (ctx, args) => {
|
|
2850
|
+
const project = ctx.requireProject();
|
|
2851
|
+
const release = await ctx.management().releases.get(project, args[0]);
|
|
2852
|
+
const diff = await ctx.management().changeSets.diff(project, release.id);
|
|
2853
|
+
emit({ release, ...diff }, () => {
|
|
2854
|
+
diag(`#${release.number} \u2014 ${release.title} (${diff.items.length} item(s))`);
|
|
2855
|
+
for (const item of diff.items) {
|
|
2856
|
+
const name = item.collection && item.slug ? `${item.collection}/${item.slug}` : item.resourceId;
|
|
2857
|
+
process.stdout.write(` ${item.operation} ${name}
|
|
2858
|
+
`);
|
|
2859
|
+
for (const change of item.changes) {
|
|
2860
|
+
process.stdout.write(` ${change.kind} ${change.path}
|
|
2861
|
+
`);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
})
|
|
2866
|
+
);
|
|
2826
2867
|
releases.command("revert").description("Generate a change set restoring the state before a release").argument("<release>", "release number or published change set id").option("--title <title>", "title for the generated change set").option("--force", "revert resources a later release already changed", false).action(
|
|
2827
2868
|
handle(async (ctx, args, opts) => {
|
|
2828
2869
|
const project = ctx.requireProject();
|
|
@@ -2854,6 +2895,244 @@ function registerReleases(program) {
|
|
|
2854
2895
|
);
|
|
2855
2896
|
}
|
|
2856
2897
|
|
|
2898
|
+
// src/commands/pull.ts
|
|
2899
|
+
import { execSync } from "child_process";
|
|
2900
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
2901
|
+
import { join as join7 } from "path";
|
|
2902
|
+
|
|
2903
|
+
// ../sdk/dist/lock.js
|
|
2904
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2905
|
+
var LOCKFILE_NAME = "myna.lock";
|
|
2906
|
+
function parseLock(source, origin = LOCKFILE_NAME) {
|
|
2907
|
+
let parsed;
|
|
2908
|
+
try {
|
|
2909
|
+
parsed = JSON.parse(source);
|
|
2910
|
+
} catch (error) {
|
|
2911
|
+
throw new Error(`${origin} is not valid JSON: ${error.message}`);
|
|
2912
|
+
}
|
|
2913
|
+
const lock = parsed;
|
|
2914
|
+
if (typeof lock?.project !== "string" || !lock.project) {
|
|
2915
|
+
throw new Error(`${origin} is missing "project".`);
|
|
2916
|
+
}
|
|
2917
|
+
if (typeof lock.release !== "number" || !Number.isInteger(lock.release) || lock.release < 0) {
|
|
2918
|
+
throw new Error(`${origin} is missing a non-negative integer "release".`);
|
|
2919
|
+
}
|
|
2920
|
+
return {
|
|
2921
|
+
project: lock.project,
|
|
2922
|
+
release: lock.release,
|
|
2923
|
+
publishedAt: typeof lock.publishedAt === "string" ? lock.publishedAt : "",
|
|
2924
|
+
...lock.title ? { title: lock.title } : {},
|
|
2925
|
+
...lock.apiUrl ? { apiUrl: lock.apiUrl } : {}
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
function formatLock(lock) {
|
|
2929
|
+
const ordered = {
|
|
2930
|
+
project: lock.project,
|
|
2931
|
+
release: lock.release,
|
|
2932
|
+
publishedAt: lock.publishedAt
|
|
2933
|
+
};
|
|
2934
|
+
if (lock.title) ordered.title = lock.title;
|
|
2935
|
+
if (lock.apiUrl) ordered.apiUrl = lock.apiUrl;
|
|
2936
|
+
return `${JSON.stringify(ordered, null, 2)}
|
|
2937
|
+
`;
|
|
2938
|
+
}
|
|
2939
|
+
function writeLock(file, lock) {
|
|
2940
|
+
writeFileSync4(file, formatLock(lock));
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// src/commands/pull.ts
|
|
2944
|
+
var MAX_DETAILED_RELEASES = 20;
|
|
2945
|
+
function lockPath(ctx) {
|
|
2946
|
+
return join7(ctx.linkedRoot ?? process.cwd(), LOCKFILE_NAME);
|
|
2947
|
+
}
|
|
2948
|
+
function readLockAt(file) {
|
|
2949
|
+
if (!existsSync6(file)) return void 0;
|
|
2950
|
+
return parseLock(readFileSync5(file, "utf8"), file);
|
|
2951
|
+
}
|
|
2952
|
+
async function latestRelease(ctx, project) {
|
|
2953
|
+
const page = await ctx.management().releases.list(project, { limit: 1 });
|
|
2954
|
+
const latest = page.data[0];
|
|
2955
|
+
if (!latest) {
|
|
2956
|
+
throw new CliError(
|
|
2957
|
+
`Project ${project} has not published a release yet. There is nothing to pin \u2014 publish a change set first.`
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
return latest;
|
|
2961
|
+
}
|
|
2962
|
+
async function releasesBetween(ctx, project, from, to) {
|
|
2963
|
+
const found = [];
|
|
2964
|
+
let cursor;
|
|
2965
|
+
for (; ; ) {
|
|
2966
|
+
const page = await ctx.management().releases.list(project, { limit: 50, ...cursor ? { cursor } : {} });
|
|
2967
|
+
for (const release of page.data) {
|
|
2968
|
+
if (release.number > to) continue;
|
|
2969
|
+
if (release.number <= from) return found.reverse();
|
|
2970
|
+
found.push(release);
|
|
2971
|
+
}
|
|
2972
|
+
if (!page.nextCursor) return found.reverse();
|
|
2973
|
+
cursor = page.nextCursor;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
function itemLabel(item) {
|
|
2977
|
+
const name = item.collection && item.slug ? `${item.collection}/${item.slug}` : item.resourceId;
|
|
2978
|
+
return `${item.operation} ${name}`;
|
|
2979
|
+
}
|
|
2980
|
+
async function pullRequestBody(ctx, project, from, to, releases, rollback) {
|
|
2981
|
+
const lines = [
|
|
2982
|
+
!from ? `Pins \`${LOCKFILE_NAME}\` for \`${project}\` to release #${to.number}.` : rollback ? `Rolls \`${LOCKFILE_NAME}\` for \`${project}\` back from release #${from.release} to #${to.number}.` : `Moves \`${LOCKFILE_NAME}\` for \`${project}\` from release #${from.release} to #${to.number}.`,
|
|
2983
|
+
"",
|
|
2984
|
+
rollback ? `${releases.length} release(s) are being undone for this repository. The content itself is untouched \u2014 nothing is unpublished \u2014 this only changes which release the build reads.` : `${releases.length} release(s). The build reads content at this release, so CI on this pull request is running against exactly the content that will ship.`
|
|
2985
|
+
];
|
|
2986
|
+
const detailed = releases.slice(-MAX_DETAILED_RELEASES);
|
|
2987
|
+
if (detailed.length < releases.length) {
|
|
2988
|
+
lines.push(
|
|
2989
|
+
"",
|
|
2990
|
+
`_Showing the newest ${detailed.length} of ${releases.length} releases. The remaining ${releases.length - detailed.length} are listed without field detail._`,
|
|
2991
|
+
"",
|
|
2992
|
+
...releases.slice(0, releases.length - detailed.length).map((r) => `- #${r.number} \u2014 ${r.title} (${r.itemCount} item(s))`)
|
|
2993
|
+
);
|
|
2994
|
+
}
|
|
2995
|
+
for (const release of detailed) {
|
|
2996
|
+
lines.push("", `### #${release.number} \u2014 ${release.title}`);
|
|
2997
|
+
if (release.description) lines.push("", release.description);
|
|
2998
|
+
let diff;
|
|
2999
|
+
try {
|
|
3000
|
+
diff = await ctx.management().changeSets.diff(project, release.id);
|
|
3001
|
+
} catch {
|
|
3002
|
+
lines.push("", `_Diff unavailable; ${release.itemCount} item(s) changed._`);
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
for (const item of diff.items) {
|
|
3006
|
+
lines.push("", `- **${itemLabel(item)}**`);
|
|
3007
|
+
if (item.changeSummary) lines.push(` - _${item.changeSummary}_`);
|
|
3008
|
+
for (const change of item.changes) lines.push(` - \`${change.path}\` ${change.kind}`);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return lines.join("\n");
|
|
3012
|
+
}
|
|
3013
|
+
function registerPull(program) {
|
|
3014
|
+
program.command("pull").description(`Pin this repository's content to a release in ${LOCKFILE_NAME}`).option("--release <n>", "release number to pin to (default: the newest)").option("--check", "report whether the pin is behind and exit non-zero if it is", false).option("--open-pr", "commit the bump onto a branch and open a pull request", false).option(
|
|
3015
|
+
"--run <command>",
|
|
3016
|
+
"shell command to run after moving the pin; anything it changes is committed with the lockfile"
|
|
3017
|
+
).option("--branch <name>", "branch to use with --open-pr (default: derived from the release)").option("--base <branch>", "branch to open the pull request against (default: the current branch)").action(
|
|
3018
|
+
handle(async (ctx, _args, opts) => {
|
|
3019
|
+
const project = ctx.requireProject();
|
|
3020
|
+
const file = lockPath(ctx);
|
|
3021
|
+
const current = readLockAt(file);
|
|
3022
|
+
if (current && current.project !== project) {
|
|
3023
|
+
throw new UsageError(
|
|
3024
|
+
`${LOCKFILE_NAME} pins project "${current.project}" but the selected project is "${project}". Pass --project ${current.project}, or delete the lockfile.`
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
const target = opts.release ? await ctx.management().releases.get(project, String(opts.release)) : await latestRelease(ctx, project);
|
|
3028
|
+
if (opts.check) {
|
|
3029
|
+
if (!current) {
|
|
3030
|
+
throw new UsageError(`No ${LOCKFILE_NAME} here. Run \`myna pull\` to create one.`);
|
|
3031
|
+
}
|
|
3032
|
+
const behind = await releasesBetween(ctx, project, current.release, target.number);
|
|
3033
|
+
emit(
|
|
3034
|
+
{ lockfile: file, release: current.release, latest: target.number, behind: behind.map((r) => r.number) },
|
|
3035
|
+
() => {
|
|
3036
|
+
if (behind.length === 0) {
|
|
3037
|
+
diag(`${LOCKFILE_NAME} is at release #${current.release} \u2014 current.`);
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
diag(`${LOCKFILE_NAME} is at release #${current.release}; #${target.number} is available.`);
|
|
3041
|
+
table(behind, [
|
|
3042
|
+
{ header: "#", value: (r) => String(r.number) },
|
|
3043
|
+
{ header: "TITLE", value: (r) => r.title },
|
|
3044
|
+
{ header: "PUBLISHED", value: (r) => r.publishedAt },
|
|
3045
|
+
{ header: "ITEMS", value: (r) => String(r.itemCount) }
|
|
3046
|
+
]);
|
|
3047
|
+
}
|
|
3048
|
+
);
|
|
3049
|
+
if (behind.length > 0) process.exitCode = 1;
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
const next = {
|
|
3053
|
+
project,
|
|
3054
|
+
release: target.number,
|
|
3055
|
+
publishedAt: target.publishedAt,
|
|
3056
|
+
title: target.title,
|
|
3057
|
+
...ctx.apiUrl && !ctx.apiUrl.startsWith("https://api.myna.sh") ? { apiUrl: ctx.apiUrl } : {}
|
|
3058
|
+
};
|
|
3059
|
+
if (current && formatLock(current) === formatLock(next)) {
|
|
3060
|
+
emit(
|
|
3061
|
+
{ lockfile: file, release: target.number, changed: false, pullRequest: null },
|
|
3062
|
+
() => diag(`${LOCKFILE_NAME} is already at release #${target.number}.`)
|
|
3063
|
+
);
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
const rollback = current !== void 0 && target.number < current.release;
|
|
3067
|
+
const releases = rollback ? await releasesBetween(ctx, project, target.number, current.release) : await releasesBetween(ctx, project, current?.release ?? 0, target.number);
|
|
3068
|
+
if (!opts.openPr) {
|
|
3069
|
+
writeLock(file, next);
|
|
3070
|
+
if (opts.run) execSync(opts.run, { cwd: ctx.linkedRoot ?? process.cwd(), stdio: "inherit" });
|
|
3071
|
+
emit(
|
|
3072
|
+
{
|
|
3073
|
+
lockfile: file,
|
|
3074
|
+
release: target.number,
|
|
3075
|
+
from: current?.release ?? null,
|
|
3076
|
+
changed: true,
|
|
3077
|
+
rollback,
|
|
3078
|
+
releases: releases.map((r) => r.number)
|
|
3079
|
+
},
|
|
3080
|
+
() => {
|
|
3081
|
+
if (!current) {
|
|
3082
|
+
diag(`${LOCKFILE_NAME}: pinned to #${target.number}.`);
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
diag(
|
|
3086
|
+
rollback ? `${LOCKFILE_NAME}: #${current.release} \u2192 #${target.number}, rolling back ${releases.length} release(s).` : `${LOCKFILE_NAME}: #${current.release} \u2192 #${target.number} (${releases.length} release(s)).`
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const root = ctx.linkedRoot ?? process.cwd();
|
|
3093
|
+
const command = opts.run;
|
|
3094
|
+
const dirty = command ? worktreeChanges(root).map((c) => c.file) : dirtyFiles(root, [file]);
|
|
3095
|
+
if (dirty.length > 0) {
|
|
3096
|
+
throw new UsageError(`Commit or stash ${dirty.join(", ")} first.`);
|
|
3097
|
+
}
|
|
3098
|
+
const body = await pullRequestBody(ctx, project, current, target, releases, rollback);
|
|
3099
|
+
writeLock(file, next);
|
|
3100
|
+
if (command) {
|
|
3101
|
+
try {
|
|
3102
|
+
execSync(command, { cwd: root, stdio: "inherit" });
|
|
3103
|
+
} catch (error) {
|
|
3104
|
+
discardChanges(root, worktreeChanges(root));
|
|
3105
|
+
throw new CliError(`--run command failed: ${error.message}`);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
const changes = worktreeChanges(root);
|
|
3109
|
+
const branch = opts.branch ?? branchForRelease(project, target.number);
|
|
3110
|
+
let result;
|
|
3111
|
+
try {
|
|
3112
|
+
result = openPullRequest({
|
|
3113
|
+
cwd: root,
|
|
3114
|
+
files: changes.map((c) => c.file),
|
|
3115
|
+
branch,
|
|
3116
|
+
base: opts.base,
|
|
3117
|
+
title: rollback ? `chore(content): roll ${project} back to release #${target.number}` : `chore(content): ${project} release #${target.number} \u2014 ${target.title}`,
|
|
3118
|
+
body
|
|
3119
|
+
});
|
|
3120
|
+
} catch (error) {
|
|
3121
|
+
discardChanges(root, changes);
|
|
3122
|
+
throw error;
|
|
3123
|
+
}
|
|
3124
|
+
if (!result.opened) discardChanges(root, changes);
|
|
3125
|
+
emit({ lockfile: file, release: target.number, from: current?.release ?? null, releases: releases.map((r) => r.number), pullRequest: result }, () => {
|
|
3126
|
+
if (!result.opened) {
|
|
3127
|
+
diag(`Branch ${result.branch} already exists \u2014 release #${target.number} is already proposed.`);
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
diag(`Opened ${result.url ?? `pull request from ${result.branch}`} against ${result.base}.`);
|
|
3131
|
+
});
|
|
3132
|
+
})
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
|
|
2857
3136
|
// src/commands/previews.ts
|
|
2858
3137
|
function previewTarget(ref) {
|
|
2859
3138
|
if (ref.startsWith("chs_")) return { changeSetId: ref };
|
|
@@ -3698,8 +3977,8 @@ function registerBilling(program) {
|
|
|
3698
3977
|
}
|
|
3699
3978
|
|
|
3700
3979
|
// src/commands/doctor.ts
|
|
3701
|
-
import { existsSync as
|
|
3702
|
-
import { join as
|
|
3980
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
3981
|
+
import { join as join8 } from "path";
|
|
3703
3982
|
|
|
3704
3983
|
// src/registry.ts
|
|
3705
3984
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
@@ -3738,7 +4017,7 @@ function installCommand(manager, version) {
|
|
|
3738
4017
|
}
|
|
3739
4018
|
|
|
3740
4019
|
// src/version.ts
|
|
3741
|
-
var VERSION = true ? "0.
|
|
4020
|
+
var VERSION = true ? "0.12.0" : "0.0.0-dev";
|
|
3742
4021
|
var IS_RELEASE_BUILD = true;
|
|
3743
4022
|
|
|
3744
4023
|
// src/commands/doctor.ts
|
|
@@ -3912,7 +4191,7 @@ async function checkOrigin(ctx, origin) {
|
|
|
3912
4191
|
}
|
|
3913
4192
|
async function checkSchema(ctx, schemaDir) {
|
|
3914
4193
|
const dir = schemaDirFor(ctx.linkedRoot, schemaDir);
|
|
3915
|
-
if (!
|
|
4194
|
+
if (!existsSync7(dir)) {
|
|
3916
4195
|
return check("schema.drift", "Local schema", "skip", `No schema directory at ${dir}.`);
|
|
3917
4196
|
}
|
|
3918
4197
|
if (!ctx.project) {
|
|
@@ -3962,7 +4241,7 @@ function findGeneratedTypes(root, depth = 4) {
|
|
|
3962
4241
|
const dirs = [];
|
|
3963
4242
|
for (const entry of entries) {
|
|
3964
4243
|
if (SCAN_IGNORE.has(entry) || entry.startsWith(".")) continue;
|
|
3965
|
-
const full =
|
|
4244
|
+
const full = join8(root, entry);
|
|
3966
4245
|
let stats;
|
|
3967
4246
|
try {
|
|
3968
4247
|
stats = statSync2(full);
|
|
@@ -3975,7 +4254,7 @@ function findGeneratedTypes(root, depth = 4) {
|
|
|
3975
4254
|
}
|
|
3976
4255
|
if (!/\.(ts|d\.ts)$/.test(entry)) continue;
|
|
3977
4256
|
try {
|
|
3978
|
-
if (looksGenerated(
|
|
4257
|
+
if (looksGenerated(readFileSync6(full, "utf8"))) return full;
|
|
3979
4258
|
} catch {
|
|
3980
4259
|
}
|
|
3981
4260
|
}
|
|
@@ -3992,16 +4271,16 @@ async function checkTypes(ctx, explicit, schemaDir) {
|
|
|
3992
4271
|
if (!file) {
|
|
3993
4272
|
return check("types.freshness", "Generated types", "skip", "No generated types file found.");
|
|
3994
4273
|
}
|
|
3995
|
-
if (!
|
|
4274
|
+
if (!existsSync7(file)) {
|
|
3996
4275
|
return check("types.freshness", "Generated types", "fail", `${file} does not exist.`);
|
|
3997
4276
|
}
|
|
3998
4277
|
const dir = schemaDirFor(ctx.linkedRoot, schemaDir);
|
|
3999
|
-
if (!
|
|
4278
|
+
if (!existsSync7(dir)) {
|
|
4000
4279
|
return check("types.freshness", "Generated types", "skip", `Found ${file} but no schema directory to compare against.`);
|
|
4001
4280
|
}
|
|
4002
4281
|
try {
|
|
4003
4282
|
const expected = generateTypesModule(await loadLocalSchemas(dir));
|
|
4004
|
-
return
|
|
4283
|
+
return readFileSync6(file, "utf8") === expected ? check("types.freshness", "Generated types", "pass", `${file} matches the local schema.`) : check(
|
|
4005
4284
|
"types.freshness",
|
|
4006
4285
|
"Generated types",
|
|
4007
4286
|
"warn",
|
|
@@ -4117,7 +4396,7 @@ function registerUpdate(program) {
|
|
|
4117
4396
|
|
|
4118
4397
|
// src/commands/sync.ts
|
|
4119
4398
|
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
4120
|
-
import { join as
|
|
4399
|
+
import { join as join9 } from "path";
|
|
4121
4400
|
async function isDirectory(path) {
|
|
4122
4401
|
const info = await stat2(path).catch(() => void 0);
|
|
4123
4402
|
return Boolean(info?.isDirectory());
|
|
@@ -4132,7 +4411,7 @@ async function planDirectories(dir, collection) {
|
|
|
4132
4411
|
const plan = [];
|
|
4133
4412
|
for (const name of children.sort()) {
|
|
4134
4413
|
if (name.startsWith(".")) continue;
|
|
4135
|
-
const full =
|
|
4414
|
+
const full = join9(dir, name);
|
|
4136
4415
|
if (await isDirectory(full)) plan.push({ collection: name, path: full });
|
|
4137
4416
|
}
|
|
4138
4417
|
if (plan.length === 0) {
|
|
@@ -4303,6 +4582,7 @@ function buildProgram() {
|
|
|
4303
4582
|
registerSync(program);
|
|
4304
4583
|
registerChanges(program);
|
|
4305
4584
|
registerReleases(program);
|
|
4585
|
+
registerPull(program);
|
|
4306
4586
|
registerPreviews(program);
|
|
4307
4587
|
registerAssets(program);
|
|
4308
4588
|
registerAdmin(program);
|