@myna-sh/cli 0.12.0 → 0.12.2

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 CHANGED
@@ -90,9 +90,9 @@ function buildQuery(query) {
90
90
  return qs ? `?${qs}` : "";
91
91
  }
92
92
  function sleep(ms, signal) {
93
- return new Promise((resolve3, reject) => {
93
+ return new Promise((resolve4, reject) => {
94
94
  if (signal?.aborted) return reject(signal.reason ?? new Error("Aborted"));
95
- const timer = setTimeout(resolve3, ms);
95
+ const timer = setTimeout(resolve4, ms);
96
96
  signal?.addEventListener(
97
97
  "abort",
98
98
  () => {
@@ -1801,9 +1801,22 @@ function branchForContent(contents) {
1801
1801
  const digest = createHash2("sha256").update(contents.join("\0")).digest("hex").slice(0, 8);
1802
1802
  return `myna/schema-${digest}`;
1803
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}`;
1804
+ function branchExists(cwd, branch) {
1805
+ try {
1806
+ execFileSync2("git", ["rev-parse", "--verify", `refs/heads/${branch}`], { cwd, stdio: "ignore" });
1807
+ return true;
1808
+ } catch {
1809
+ }
1810
+ try {
1811
+ const out = execFileSync2("git", ["ls-remote", "--heads", "origin", branch], {
1812
+ cwd,
1813
+ encoding: "utf8",
1814
+ stdio: ["ignore", "pipe", "ignore"]
1815
+ });
1816
+ return out.trim().length > 0;
1817
+ } catch {
1818
+ return false;
1819
+ }
1807
1820
  }
1808
1821
  function openPullRequest(input) {
1809
1822
  if (!available("git")) throw new CliError("git is required to open a pull request.");
@@ -1817,18 +1830,9 @@ function openPullRequest(input) {
1817
1830
  if (base === "HEAD") {
1818
1831
  throw new CliError("HEAD is detached; pass --base to say which branch to open the pull request against.");
1819
1832
  }
1820
- const exists = (() => {
1821
- try {
1822
- execFileSync2("git", ["rev-parse", "--verify", `refs/heads/${input.branch}`], {
1823
- cwd: input.cwd,
1824
- stdio: "ignore"
1825
- });
1826
- return true;
1827
- } catch {
1828
- return false;
1829
- }
1830
- })();
1831
- if (exists) return { opened: false, branch: input.branch, base, url: null, reason: "branch-exists" };
1833
+ if (branchExists(input.cwd, input.branch)) {
1834
+ return { opened: false, branch: input.branch, base, url: null, reason: "branch-exists" };
1835
+ }
1832
1836
  git(["checkout", "-b", input.branch], input.cwd);
1833
1837
  let pushed = false;
1834
1838
  try {
@@ -1855,10 +1859,32 @@ function dirtyFiles(cwd, paths) {
1855
1859
  return run("git", ["status", "--porcelain", "--", ...paths], cwd).split("\n").filter(Boolean).map((line) => line.slice(3).trim());
1856
1860
  }
1857
1861
  function repositoryRoot(cwd) {
1858
- return git(["rev-parse", "--show-toplevel"], cwd);
1862
+ try {
1863
+ return execFileSync2("git", ["rev-parse", "--show-toplevel"], {
1864
+ cwd,
1865
+ encoding: "utf8",
1866
+ stdio: ["ignore", "pipe", "ignore"]
1867
+ }).trim();
1868
+ } catch {
1869
+ return void 0;
1870
+ }
1871
+ }
1872
+ function trackedLockfiles(cwd) {
1873
+ const root = repositoryRoot(cwd);
1874
+ if (!root) return [];
1875
+ try {
1876
+ return execFileSync2("git", ["ls-files", "--full-name", "*myna.lock", "myna.lock"], {
1877
+ cwd: root,
1878
+ encoding: "utf8",
1879
+ stdio: ["ignore", "pipe", "ignore"]
1880
+ }).split("\n").filter(Boolean).map((f) => join4(root, f));
1881
+ } catch {
1882
+ return [];
1883
+ }
1859
1884
  }
1860
1885
  function worktreeChanges(cwd) {
1861
1886
  const root = repositoryRoot(cwd);
1887
+ if (!root) throw new CliError(`${cwd} is not inside a git repository.`);
1862
1888
  return run("git", ["status", "--porcelain"], cwd).split("\n").filter(Boolean).map((line) => ({
1863
1889
  file: join4(root, line.slice(3).trim()),
1864
1890
  untracked: line.startsWith("??")
@@ -2898,11 +2924,25 @@ function registerReleases(program) {
2898
2924
  // src/commands/pull.ts
2899
2925
  import { execSync } from "child_process";
2900
2926
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
2901
- import { join as join7 } from "path";
2927
+ import { join as join8 } from "path";
2902
2928
 
2903
2929
  // ../sdk/dist/lock.js
2904
2930
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
2931
+ import { dirname as dirname2, join as join7, resolve as resolve3 } from "path";
2905
2932
  var LOCKFILE_NAME = "myna.lock";
2933
+ function formatLock(lock) {
2934
+ const ordered = {
2935
+ project: lock.project,
2936
+ release: lock.release,
2937
+ publishedAt: lock.publishedAt
2938
+ };
2939
+ if (lock.title)
2940
+ ordered.title = lock.title;
2941
+ if (lock.apiUrl)
2942
+ ordered.apiUrl = lock.apiUrl;
2943
+ return `${JSON.stringify(ordered, null, 2)}
2944
+ `;
2945
+ }
2906
2946
  function parseLock(source, origin = LOCKFILE_NAME) {
2907
2947
  let parsed;
2908
2948
  try {
@@ -2925,29 +2965,226 @@ function parseLock(source, origin = LOCKFILE_NAME) {
2925
2965
  ...lock.apiUrl ? { apiUrl: lock.apiUrl } : {}
2926
2966
  };
2927
2967
  }
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
- `;
2968
+ var LOCKFILE_NAME2 = "myna.lock";
2969
+ function formatLock2(lock) {
2970
+ return formatLock(lock);
2971
+ }
2972
+ function parseLock2(source, origin = LOCKFILE_NAME2) {
2973
+ return parseLock(source, origin);
2974
+ }
2975
+ function findLockfile(startDir = process.cwd()) {
2976
+ let dir = resolve3(startDir);
2977
+ for (; ; ) {
2978
+ const file = join7(dir, LOCKFILE_NAME2);
2979
+ if (existsSync5(file)) return file;
2980
+ const parent = dirname2(dir);
2981
+ if (parent === dir) return void 0;
2982
+ dir = parent;
2983
+ }
2938
2984
  }
2939
2985
  function writeLock(file, lock) {
2940
- writeFileSync4(file, formatLock(lock));
2986
+ writeFileSync4(file, formatLock2(lock));
2941
2987
  }
2942
2988
 
2943
- // src/commands/pull.ts
2989
+ // ../shared/dist/ids.js
2990
+ var ID_PREFIXES = {
2991
+ user: "usr",
2992
+ oauthAccount: "oau",
2993
+ session: "ses",
2994
+ organization: "org",
2995
+ organizationMember: "mem",
2996
+ organizationInvitation: "inv",
2997
+ project: "prj",
2998
+ projectOrigin: "por",
2999
+ apiKey: "key",
3000
+ collection: "col",
3001
+ collectionVersion: "clv",
3002
+ entry: "ent",
3003
+ entrySlugAlias: "als",
3004
+ entryRevision: "rev",
3005
+ changeSet: "chs",
3006
+ changeSetItem: "csi",
3007
+ changeSetReview: "csr",
3008
+ changeSetComment: "csc",
3009
+ changeSetCheck: "chk",
3010
+ projectCheck: "pck",
3011
+ asset: "ast",
3012
+ assetUpload: "upl",
3013
+ savedView: "viw",
3014
+ previewToken: "prv",
3015
+ webhookEndpoint: "whk",
3016
+ webhookEvent: "whe",
3017
+ webhookDelivery: "whd",
3018
+ auditEvent: "aud",
3019
+ job: "job",
3020
+ idempotencyKey: "idm",
3021
+ billingCustomer: "bcu",
3022
+ billingSubscription: "bsu",
3023
+ billingProviderEvent: "evt",
3024
+ usagePeriod: "usp",
3025
+ mcpClient: "mcl",
3026
+ mcpGrant: "mgr",
3027
+ mcpAuthorizationCode: "mac",
3028
+ mcpToken: "mtk",
3029
+ deviceAuthorization: "dev",
3030
+ githubInstallation: "ghi",
3031
+ projectRepository: "prp"
3032
+ };
3033
+ var PREFIX_SET = new Set(Object.values(ID_PREFIXES));
3034
+
3035
+ // ../shared/dist/dates.js
3036
+ var HOURS = 60 * 60;
3037
+ var DAYS = 24 * 60 * 60;
3038
+
3039
+ // ../shared/dist/origins.js
3040
+ function normalizeOrigin(value) {
3041
+ try {
3042
+ const url = new URL(value.trim());
3043
+ if (url.protocol !== "http:" && url.protocol !== "https:")
3044
+ return void 0;
3045
+ return url.origin;
3046
+ } catch {
3047
+ return void 0;
3048
+ }
3049
+ }
3050
+
3051
+ // ../shared/dist/plans.js
3052
+ var GB = 1024 ** 3;
3053
+ var MB = 1024 ** 2;
3054
+ var UNLIMITED = Number.MAX_SAFE_INTEGER;
3055
+ var PLANS = {
3056
+ free: {
3057
+ key: "free",
3058
+ priceEurMonthly: 0,
3059
+ priceEurYearly: 0,
3060
+ maxProjects: 2,
3061
+ maxMembers: 2,
3062
+ storageBytes: 1 * GB,
3063
+ monthlyBandwidthBytes: 25 * GB,
3064
+ monthlyPublicApiRequests: 1e4,
3065
+ maxWebhookEndpoints: 1,
3066
+ revisionRetentionDays: 30,
3067
+ maxUploadBytes: 50 * MB
3068
+ },
3069
+ pro: {
3070
+ key: "pro",
3071
+ priceEurMonthly: 9,
3072
+ priceEurYearly: 90,
3073
+ maxProjects: 10,
3074
+ maxMembers: 5,
3075
+ storageBytes: 10 * GB,
3076
+ monthlyBandwidthBytes: 250 * GB,
3077
+ monthlyPublicApiRequests: 5e5,
3078
+ maxWebhookEndpoints: 10,
3079
+ revisionRetentionDays: null,
3080
+ maxUploadBytes: 250 * MB
3081
+ },
3082
+ /**
3083
+ * Myna's own organizations — the changelog, and anything else we run on the
3084
+ * product we sell. It is never sold, never offered at checkout, and cannot be
3085
+ * reached through the API: `scripts/bootstrap-internal-org.mjs` writes it
3086
+ * directly, and billing refuses to move an organization on or off it.
3087
+ */
3088
+ internal: {
3089
+ key: "internal",
3090
+ priceEurMonthly: 0,
3091
+ priceEurYearly: null,
3092
+ maxProjects: UNLIMITED,
3093
+ maxMembers: UNLIMITED,
3094
+ storageBytes: UNLIMITED,
3095
+ monthlyBandwidthBytes: UNLIMITED,
3096
+ monthlyPublicApiRequests: UNLIMITED,
3097
+ maxWebhookEndpoints: UNLIMITED,
3098
+ revisionRetentionDays: null,
3099
+ maxUploadBytes: UNLIMITED
3100
+ }
3101
+ };
3102
+
3103
+ // ../shared/dist/rank.js
3104
+ var DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
3105
+ var MIN_DIGIT = DIGITS[0];
3106
+ var LAST_INDEX = DIGITS.length - 1;
3107
+ var MID_DIGIT = DIGITS[Math.floor(DIGITS.length / 2)];
3108
+
3109
+ // ../shared/dist/release.js
3110
+ function parseSemVer(version) {
3111
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
3112
+ if (!match)
3113
+ return void 0;
3114
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
3115
+ }
3116
+ function compareSemVer(a, b) {
3117
+ const left = parseSemVer(a);
3118
+ const right = parseSemVer(b);
3119
+ if (!left || !right)
3120
+ throw new Error(`Cannot compare versions "${a}" and "${b}".`);
3121
+ return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
3122
+ }
3123
+
3124
+ // ../shared/dist/lock.js
3125
+ var LOCKFILE_NAME3 = "myna.lock";
3126
+ function branchForRelease(project, release) {
3127
+ const slug = project.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "project";
3128
+ return `myna/content-${slug}-r${release}`;
3129
+ }
2944
3130
  var MAX_DETAILED_RELEASES = 20;
3131
+ function releasePullRequestTitle(input) {
3132
+ return input.rollback ? `chore(content): roll ${input.project} back to release #${input.to.number}` : `chore(content): ${input.project} release #${input.to.number} \u2014 ${input.to.title}`;
3133
+ }
3134
+ function releasePullRequestBody(input) {
3135
+ const { project, from, to, releases, rollback } = input;
3136
+ const lines = [
3137
+ from === null ? `Pins \`${LOCKFILE_NAME3}\` for \`${project}\` to release #${to.number}.` : rollback ? `Rolls \`${LOCKFILE_NAME3}\` for \`${project}\` back from release #${from} to #${to.number}.` : `Moves \`${LOCKFILE_NAME3}\` for \`${project}\` from release #${from} to #${to.number}.`,
3138
+ "",
3139
+ 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.`
3140
+ ];
3141
+ const detailed = releases.slice(-MAX_DETAILED_RELEASES);
3142
+ if (detailed.length < releases.length) {
3143
+ const omitted = releases.length - detailed.length;
3144
+ lines.push("", `_Showing the newest ${detailed.length} of ${releases.length} releases. The remaining ${omitted} are listed without field detail._`, "", ...releases.slice(0, omitted).map((r) => `- #${r.number} \u2014 ${r.title} (${r.itemCount ?? 0} item(s))`));
3145
+ }
3146
+ for (const release of detailed) {
3147
+ lines.push("", `### #${release.number} \u2014 ${release.title}`);
3148
+ if (release.description)
3149
+ lines.push("", release.description);
3150
+ if (!release.diff) {
3151
+ lines.push("", `_Diff unavailable; ${release.itemCount ?? 0} item(s) changed._`);
3152
+ continue;
3153
+ }
3154
+ for (const item of release.diff.items) {
3155
+ const name = item.collection && item.slug ? `${item.collection}/${item.slug}` : item.resourceId;
3156
+ lines.push("", `- **${item.operation} ${name}**`);
3157
+ if (item.changeSummary)
3158
+ lines.push(` - _${item.changeSummary}_`);
3159
+ for (const change of item.changes)
3160
+ lines.push(` - \`${change.path}\` ${change.kind}`);
3161
+ }
3162
+ }
3163
+ return lines.join("\n");
3164
+ }
3165
+
3166
+ // src/commands/pull.ts
2945
3167
  function lockPath(ctx) {
2946
- return join7(ctx.linkedRoot ?? process.cwd(), LOCKFILE_NAME);
3168
+ const cwd = ctx.linkedRoot ?? process.cwd();
3169
+ const found = findLockfile(cwd);
3170
+ if (found) {
3171
+ const boundary = repositoryRoot(cwd);
3172
+ if (!boundary || found.startsWith(boundary)) return found;
3173
+ }
3174
+ return join8(cwd, LOCKFILE_NAME2);
3175
+ }
3176
+ function assertOneLockfile(cwd, active) {
3177
+ const all = trackedLockfiles(cwd);
3178
+ if (all.length <= 1) return;
3179
+ throw new UsageError(
3180
+ `This repository has ${all.length} lockfiles:
3181
+ ` + all.map((f) => ` ${f}${f === active ? " (the one this would move)" : ""}`).join("\n") + `
3182
+ Reads take the one nearest the build, which may not be that one. Delete the extras and keep a single ${LOCKFILE_NAME2}.`
3183
+ );
2947
3184
  }
2948
3185
  function readLockAt(file) {
2949
3186
  if (!existsSync6(file)) return void 0;
2950
- return parseLock(readFileSync5(file, "utf8"), file);
3187
+ return parseLock2(readFileSync5(file, "utf8"), file);
2951
3188
  }
2952
3189
  async function latestRelease(ctx, project) {
2953
3190
  const page = await ctx.management().releases.list(project, { limit: 1 });
@@ -2973,71 +3210,55 @@ async function releasesBetween(ctx, project, from, to) {
2973
3210
  cursor = page.nextCursor;
2974
3211
  }
2975
3212
  }
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");
3213
+ async function pullRequestText(ctx, project, from, to, releases, rollback) {
3214
+ const summaries = await Promise.all(
3215
+ releases.map(async (release) => ({
3216
+ number: release.number,
3217
+ title: release.title,
3218
+ description: release.description,
3219
+ itemCount: release.itemCount,
3220
+ // A release whose diff cannot be read is still a release worth naming.
3221
+ diff: await ctx.management().changeSets.diff(project, release.id).catch(() => null)
3222
+ }))
3223
+ );
3224
+ const input = {
3225
+ project,
3226
+ from: from?.release ?? null,
3227
+ to: { number: to.number, title: to.title, description: to.description, itemCount: to.itemCount },
3228
+ releases: summaries,
3229
+ rollback
3230
+ };
3231
+ return { title: releasePullRequestTitle(input), body: releasePullRequestBody(input) };
3012
3232
  }
3013
3233
  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(
3234
+ program.command("pull").description(`Pin this repository's content to a release in ${LOCKFILE_NAME2}`).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
3235
  "--run <command>",
3016
3236
  "shell command to run after moving the pin; anything it changes is committed with the lockfile"
3017
3237
  ).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
3238
  handle(async (ctx, _args, opts) => {
3019
3239
  const project = ctx.requireProject();
3020
3240
  const file = lockPath(ctx);
3241
+ assertOneLockfile(ctx.linkedRoot ?? process.cwd(), file);
3021
3242
  const current = readLockAt(file);
3022
3243
  if (current && current.project !== project) {
3023
3244
  throw new UsageError(
3024
- `${LOCKFILE_NAME} pins project "${current.project}" but the selected project is "${project}". Pass --project ${current.project}, or delete the lockfile.`
3245
+ `${LOCKFILE_NAME2} pins project "${current.project}" but the selected project is "${project}". Pass --project ${current.project}, or delete the lockfile.`
3025
3246
  );
3026
3247
  }
3027
3248
  const target = opts.release ? await ctx.management().releases.get(project, String(opts.release)) : await latestRelease(ctx, project);
3028
3249
  if (opts.check) {
3029
3250
  if (!current) {
3030
- throw new UsageError(`No ${LOCKFILE_NAME} here. Run \`myna pull\` to create one.`);
3251
+ throw new UsageError(`No ${LOCKFILE_NAME2} here. Run \`myna pull\` to create one.`);
3031
3252
  }
3032
3253
  const behind = await releasesBetween(ctx, project, current.release, target.number);
3033
3254
  emit(
3034
3255
  { lockfile: file, release: current.release, latest: target.number, behind: behind.map((r) => r.number) },
3035
3256
  () => {
3036
3257
  if (behind.length === 0) {
3037
- diag(`${LOCKFILE_NAME} is at release #${current.release} \u2014 current.`);
3258
+ diag(`${LOCKFILE_NAME2} is at release #${current.release} \u2014 current.`);
3038
3259
  return;
3039
3260
  }
3040
- diag(`${LOCKFILE_NAME} is at release #${current.release}; #${target.number} is available.`);
3261
+ diag(`${LOCKFILE_NAME2} is at release #${current.release}; #${target.number} is available.`);
3041
3262
  table(behind, [
3042
3263
  { header: "#", value: (r) => String(r.number) },
3043
3264
  { header: "TITLE", value: (r) => r.title },
@@ -3056,10 +3277,10 @@ function registerPull(program) {
3056
3277
  title: target.title,
3057
3278
  ...ctx.apiUrl && !ctx.apiUrl.startsWith("https://api.myna.sh") ? { apiUrl: ctx.apiUrl } : {}
3058
3279
  };
3059
- if (current && formatLock(current) === formatLock(next)) {
3280
+ if (current && formatLock2(current) === formatLock2(next)) {
3060
3281
  emit(
3061
3282
  { lockfile: file, release: target.number, changed: false, pullRequest: null },
3062
- () => diag(`${LOCKFILE_NAME} is already at release #${target.number}.`)
3283
+ () => diag(`${LOCKFILE_NAME2} is already at release #${target.number}.`)
3063
3284
  );
3064
3285
  return;
3065
3286
  }
@@ -3079,11 +3300,11 @@ function registerPull(program) {
3079
3300
  },
3080
3301
  () => {
3081
3302
  if (!current) {
3082
- diag(`${LOCKFILE_NAME}: pinned to #${target.number}.`);
3303
+ diag(`${LOCKFILE_NAME2}: pinned to #${target.number}.`);
3083
3304
  return;
3084
3305
  }
3085
3306
  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)).`
3307
+ rollback ? `${LOCKFILE_NAME2}: #${current.release} \u2192 #${target.number}, rolling back ${releases.length} release(s).` : `${LOCKFILE_NAME2}: #${current.release} \u2192 #${target.number} (${releases.length} release(s)).`
3087
3308
  );
3088
3309
  }
3089
3310
  );
@@ -3095,7 +3316,7 @@ function registerPull(program) {
3095
3316
  if (dirty.length > 0) {
3096
3317
  throw new UsageError(`Commit or stash ${dirty.join(", ")} first.`);
3097
3318
  }
3098
- const body = await pullRequestBody(ctx, project, current, target, releases, rollback);
3319
+ const text = await pullRequestText(ctx, project, current, target, releases, rollback);
3099
3320
  writeLock(file, next);
3100
3321
  if (command) {
3101
3322
  try {
@@ -3114,8 +3335,8 @@ function registerPull(program) {
3114
3335
  files: changes.map((c) => c.file),
3115
3336
  branch,
3116
3337
  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
3338
+ title: text.title,
3339
+ body: text.body
3119
3340
  });
3120
3341
  } catch (error) {
3121
3342
  discardChanges(root, changes);
@@ -3303,139 +3524,6 @@ function registerAssets(program) {
3303
3524
  );
3304
3525
  }
3305
3526
 
3306
- // ../shared/dist/ids.js
3307
- var ID_PREFIXES = {
3308
- user: "usr",
3309
- oauthAccount: "oau",
3310
- session: "ses",
3311
- organization: "org",
3312
- organizationMember: "mem",
3313
- organizationInvitation: "inv",
3314
- project: "prj",
3315
- projectOrigin: "por",
3316
- apiKey: "key",
3317
- collection: "col",
3318
- collectionVersion: "clv",
3319
- entry: "ent",
3320
- entrySlugAlias: "als",
3321
- entryRevision: "rev",
3322
- changeSet: "chs",
3323
- changeSetItem: "csi",
3324
- changeSetReview: "csr",
3325
- changeSetComment: "csc",
3326
- changeSetCheck: "chk",
3327
- projectCheck: "pck",
3328
- asset: "ast",
3329
- assetUpload: "upl",
3330
- savedView: "viw",
3331
- previewToken: "prv",
3332
- webhookEndpoint: "whk",
3333
- webhookEvent: "whe",
3334
- webhookDelivery: "whd",
3335
- auditEvent: "aud",
3336
- job: "job",
3337
- idempotencyKey: "idm",
3338
- billingCustomer: "bcu",
3339
- billingSubscription: "bsu",
3340
- billingProviderEvent: "evt",
3341
- usagePeriod: "usp",
3342
- mcpClient: "mcl",
3343
- mcpGrant: "mgr",
3344
- mcpAuthorizationCode: "mac",
3345
- mcpToken: "mtk",
3346
- deviceAuthorization: "dev"
3347
- };
3348
- var PREFIX_SET = new Set(Object.values(ID_PREFIXES));
3349
-
3350
- // ../shared/dist/dates.js
3351
- var HOURS = 60 * 60;
3352
- var DAYS = 24 * 60 * 60;
3353
-
3354
- // ../shared/dist/origins.js
3355
- function normalizeOrigin(value) {
3356
- try {
3357
- const url = new URL(value.trim());
3358
- if (url.protocol !== "http:" && url.protocol !== "https:")
3359
- return void 0;
3360
- return url.origin;
3361
- } catch {
3362
- return void 0;
3363
- }
3364
- }
3365
-
3366
- // ../shared/dist/plans.js
3367
- var GB = 1024 ** 3;
3368
- var MB = 1024 ** 2;
3369
- var UNLIMITED = Number.MAX_SAFE_INTEGER;
3370
- var PLANS = {
3371
- free: {
3372
- key: "free",
3373
- priceEurMonthly: 0,
3374
- priceEurYearly: 0,
3375
- maxProjects: 2,
3376
- maxMembers: 2,
3377
- storageBytes: 1 * GB,
3378
- monthlyBandwidthBytes: 25 * GB,
3379
- monthlyPublicApiRequests: 1e4,
3380
- maxWebhookEndpoints: 1,
3381
- revisionRetentionDays: 30,
3382
- maxUploadBytes: 50 * MB
3383
- },
3384
- pro: {
3385
- key: "pro",
3386
- priceEurMonthly: 9,
3387
- priceEurYearly: 90,
3388
- maxProjects: 10,
3389
- maxMembers: 5,
3390
- storageBytes: 10 * GB,
3391
- monthlyBandwidthBytes: 250 * GB,
3392
- monthlyPublicApiRequests: 5e5,
3393
- maxWebhookEndpoints: 10,
3394
- revisionRetentionDays: null,
3395
- maxUploadBytes: 250 * MB
3396
- },
3397
- /**
3398
- * Myna's own organizations — the changelog, and anything else we run on the
3399
- * product we sell. It is never sold, never offered at checkout, and cannot be
3400
- * reached through the API: `scripts/bootstrap-internal-org.mjs` writes it
3401
- * directly, and billing refuses to move an organization on or off it.
3402
- */
3403
- internal: {
3404
- key: "internal",
3405
- priceEurMonthly: 0,
3406
- priceEurYearly: null,
3407
- maxProjects: UNLIMITED,
3408
- maxMembers: UNLIMITED,
3409
- storageBytes: UNLIMITED,
3410
- monthlyBandwidthBytes: UNLIMITED,
3411
- monthlyPublicApiRequests: UNLIMITED,
3412
- maxWebhookEndpoints: UNLIMITED,
3413
- revisionRetentionDays: null,
3414
- maxUploadBytes: UNLIMITED
3415
- }
3416
- };
3417
-
3418
- // ../shared/dist/rank.js
3419
- var DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
3420
- var MIN_DIGIT = DIGITS[0];
3421
- var LAST_INDEX = DIGITS.length - 1;
3422
- var MID_DIGIT = DIGITS[Math.floor(DIGITS.length / 2)];
3423
-
3424
- // ../shared/dist/release.js
3425
- function parseSemVer(version) {
3426
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
3427
- if (!match)
3428
- return void 0;
3429
- return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
3430
- }
3431
- function compareSemVer(a, b) {
3432
- const left = parseSemVer(a);
3433
- const right = parseSemVer(b);
3434
- if (!left || !right)
3435
- throw new Error(`Cannot compare versions "${a}" and "${b}".`);
3436
- return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
3437
- }
3438
-
3439
3527
  // src/commands/admin.ts
3440
3528
  function csv(value) {
3441
3529
  return value.split(",").map((s) => s.trim()).filter(Boolean);
@@ -3978,7 +4066,7 @@ function registerBilling(program) {
3978
4066
 
3979
4067
  // src/commands/doctor.ts
3980
4068
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3981
- import { join as join8 } from "path";
4069
+ import { join as join9 } from "path";
3982
4070
 
3983
4071
  // src/registry.ts
3984
4072
  var NPM_REGISTRY = "https://registry.npmjs.org";
@@ -4017,7 +4105,7 @@ function installCommand(manager, version) {
4017
4105
  }
4018
4106
 
4019
4107
  // src/version.ts
4020
- var VERSION = true ? "0.12.0" : "0.0.0-dev";
4108
+ var VERSION = true ? "0.12.2" : "0.0.0-dev";
4021
4109
  var IS_RELEASE_BUILD = true;
4022
4110
 
4023
4111
  // src/commands/doctor.ts
@@ -4241,7 +4329,7 @@ function findGeneratedTypes(root, depth = 4) {
4241
4329
  const dirs = [];
4242
4330
  for (const entry of entries) {
4243
4331
  if (SCAN_IGNORE.has(entry) || entry.startsWith(".")) continue;
4244
- const full = join8(root, entry);
4332
+ const full = join9(root, entry);
4245
4333
  let stats;
4246
4334
  try {
4247
4335
  stats = statSync2(full);
@@ -4396,7 +4484,7 @@ function registerUpdate(program) {
4396
4484
 
4397
4485
  // src/commands/sync.ts
4398
4486
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
4399
- import { join as join9 } from "path";
4487
+ import { join as join10 } from "path";
4400
4488
  async function isDirectory(path) {
4401
4489
  const info = await stat2(path).catch(() => void 0);
4402
4490
  return Boolean(info?.isDirectory());
@@ -4411,7 +4499,7 @@ async function planDirectories(dir, collection) {
4411
4499
  const plan = [];
4412
4500
  for (const name of children.sort()) {
4413
4501
  if (name.startsWith(".")) continue;
4414
- const full = join9(dir, name);
4502
+ const full = join10(dir, name);
4415
4503
  if (await isDirectory(full)) plan.push({ collection: name, path: full });
4416
4504
  }
4417
4505
  if (plan.length === 0) {