livelocale 0.10.0 → 0.11.0-next.10

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 CHANGED
@@ -387,9 +387,12 @@ command line) and needs `GITHUB_TOKEN`.
387
387
  - Only a maintainer (OWNER, MEMBER or COLLABORATOR, or write access) makes it write. Anyone else
388
388
  gets the reply "Waiting for a maintainer: comment `/livelocale apply`".
389
389
  - It writes one commit on a `livelocale/issue-<n>` branch (`livelocale/run-<id>` for a manual
390
- run), opens or reuses the pull request, and comments the link on the issue. When the repository
391
- does not let GitHub Actions open pull requests, the comment carries a one-click compare link
392
- instead.
390
+ run), opens or reuses the pull request, and comments the link on the issue. While that pull
391
+ request is open, a later apply on the same issue adds one commit to it. Once it is merged or
392
+ closed, the next apply opens a new pull request from `livelocale/issue-<n>-<runId>` (with `-2`,
393
+ `-3`, … while that name is taken), and later applies add to that one while it is open; a
394
+ branch is never force-pushed. When the repository does not let GitHub Actions open pull
395
+ requests, the comment carries a one-click compare link instead.
393
396
  - `--locales` and `--dir` are relative to the repository root. `--branches` lists the branches a
394
397
  link may target (default: the repository's default branch); a link made from any other branch
395
398
  targets the default branch, and the comment says so.
@@ -445,12 +448,12 @@ liveLocaleVite({
445
448
  });
446
449
  ```
447
450
 
448
- | Option | Default | What it does |
449
- | -------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
450
- | `share` | `false` | Write the share Studio into `<outDir>/livelocale/` on `vite build`. SSR and library builds are skipped. |
451
- | `exposeRepo` | `false` | Put the GitHub `owner/repo` into the published manifest and the change link. Leave it off for a private repository. |
452
- | `sourceLocale` | `en` when present, else the first locale | The locale placeholders are checked against. |
453
- | `shareBase` | Vite `base` | The path the HTML is served from. Set it when `base` is a full URL (a CDN), which the build otherwise rejects with `BAD_BASE`, or differs. |
451
+ | Option | Default | What it does |
452
+ | -------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
453
+ | `share` | `false` | Write the share Studio into `<outDir>/livelocale/` on `vite build`. SSR and library builds are skipped. |
454
+ | `exposeRepo` | `false` | Put the GitHub `owner/repo` into the published manifest and the change link. Leave it off for a private repository. |
455
+ | `sourceLocale` | `en` when present, else the first locale | The locale placeholders are checked against. |
456
+ | `shareBase` | Vite `base` | The path the HTML is served from. Set it when `base` is a full URL (a CDN), which the build otherwise rejects with `BAD_BASE`, or when the HTML is served from a path other than `base`. |
454
457
 
455
458
  A relative `base` (`./`) links `flag.js` per HTML file and prints a warning: pages served by a
456
459
  client-side route fallback cannot load it, so prefer an absolute `base` for apps with such routes.
@@ -517,7 +520,8 @@ it. Without `reference` only the syntax check runs.
517
520
 
518
521
  ## 🔗 Share link on any host
519
522
 
520
- Translators fix text on your live site with no account, and every fix becomes a pull request with one click.
523
+ Translators fix text on your live site with no account, and every fix becomes a pull request with
524
+ one click.
521
525
 
522
526
  A share link needs no server, no account and no credential anywhere. The Studio ships inside your
523
527
  build at `<base>livelocale/`, so any host that serves the site serves the Studio too: Apache,
@@ -632,22 +636,21 @@ on the real page and a before/after table with the check results. Then turn it i
632
636
  - With issues turned off, or for a link you would rather not file: **Actions → LiveLocale → Run
633
637
  workflow**, paste the link and run it. The result is on the run's summary page.
634
638
  - **The trust gate.** The workflow writes only for a maintainer: the issue author or the
635
- commenter must be an owner, member or collaborator, or have write access. For anyone else it
639
+ commenter must be an owner, member or collaborator, or have write access. In an organization
640
+ that includes any member or invited collaborator, even one with read-only access: they can turn a
641
+ link into a pull request, but only people with write access can merge it. For anyone else it
636
642
  replies "Waiting for a maintainer: comment `/livelocale apply`", and nothing is written until a
637
643
  maintainer posts that comment on the issue. Edits the translator saved with "Save anyway" are
638
644
  skipped as `needs --force`; comment `/livelocale apply --force` to include them.
639
- - **Who can trigger it.** The Action trusts the issue author or the commenter when GitHub marks
640
- them OWNER, MEMBER or COLLABORATOR on the repository, or when they have write access. In an
641
- organization that means any member or invited collaborator, even one with read-only access, can
642
- turn a link into a pull request, but only people with write access can merge it.
643
645
  - **Base branches.** By default every pull request targets the default branch. To let links made
644
646
  from other builds (for example a `staging` deployment) target their own branch, list the
645
647
  branches with `npx livelocale init --share --branches main,staging`. A link from any other
646
648
  branch targets the default branch, and the comment says so. A pull request into the default
647
649
  branch says `Closes #n`, so merging it closes the issue; into another branch it says `Refs #n`,
648
650
  because GitHub only closes issues from the default branch, and LiveLocale closes the issue
649
- itself when that pull request is merged or closed.
650
- Also merge .github/workflows/livelocale.yml into each extra branch; pull requests into it run workflows from that branch, so LiveLocale cannot close their issues otherwise.
651
+ itself when that pull request is merged or closed. Also merge `.github/workflows/livelocale.yml`
652
+ into each extra branch: pull requests into it run workflows from that branch, so LiveLocale cannot
653
+ close their issues otherwise.
651
654
 
652
655
  **On any git host, from your machine:**
653
656
 
@@ -658,8 +661,9 @@ npx livelocale apply "<link or file>" --pr
658
661
  This works with your own `git` login: it commits only the locale files on a new
659
662
  `livelocale/apply-<time>` branch and pushes it. On GitHub it opens the pull request with `gh`;
660
663
  elsewhere it prints the branch to open a pull or merge request from. Every edit is checked again
661
- against your own files first. For a long link, use `npx livelocale apply --clipboard`, or pass `-` and pipe
662
- the link in (`npx livelocale apply - < changes.txt`). See [`apply`](#apply) for every option.
664
+ against your own files first. For a long link, use `npx livelocale apply --clipboard`, or pass `-`
665
+ and pipe the link in (`npx livelocale apply - < changes.txt`). See [`apply`](#apply) for every
666
+ option.
663
667
 
664
668
  #### Run CI on LiveLocale PRs without the approval click
665
669
 
@@ -669,8 +673,8 @@ GitHub App token start CI as usual. To use one:
669
673
 
670
674
  1. Create a GitHub App with **Contents**, **Pull requests** and **Issues** set to read and write,
671
675
  and install it on the repository.
672
- 2. Store its ID as the repository variable `LIVELOCALE_APP_ID` and its private key as the secret
673
- `LIVELOCALE_APP_PRIVATE_KEY`.
676
+ 2. Store its Client ID (on the App's settings page) as the repository variable
677
+ `LIVELOCALE_APP_CLIENT_ID` and its private key as the secret `LIVELOCALE_APP_PRIVATE_KEY`.
674
678
  3. In `.github/workflows/livelocale.yml`, add this step before the `apply` step and give its token
675
679
  to the `apply` step as `GITHUB_TOKEN`:
676
680
 
@@ -679,7 +683,7 @@ GitHub App token start CI as usual. To use one:
679
683
  uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
680
684
  with:
681
685
  {
682
- app-id: '${{ vars.LIVELOCALE_APP_ID }}',
686
+ client-id: '${{ vars.LIVELOCALE_APP_CLIENT_ID }}',
683
687
  private-key: '${{ secrets.LIVELOCALE_APP_PRIVATE_KEY }}',
684
688
  }
685
689
  - run: npx --yes livelocale@X.Y.Z apply --github-event --locales locales --source en --branches main
@@ -699,7 +703,8 @@ whose translators already have GitHub access; with the default, translators use
699
703
 
700
704
  ### What "Sent" means
701
705
 
702
- In this mode "Sent" means the translator handed the link over; it is not a pull request until a developer applies it.
706
+ In this mode "Sent" means the translator handed the link over; it is not a pull request until a
707
+ developer applies it.
703
708
 
704
709
  ### Hosting notes
705
710
 
@@ -717,9 +722,8 @@ In this mode "Sent" means the translator handed the link over; it is not a pull
717
722
  - **PWA:** a Workbox service worker (vite-plugin-pwa, workbox-webpack-plugin) can answer the Studio
718
723
  with your app shell. Add `navigateFallbackDenylist: [/^\/livelocale\//]` to its Workbox options.
719
724
  - **Relative bases** (`base: './'`, CRA `homepage: '.'`): pages served by a client-side route
720
- fallback cannot load `flag.js`, so pack prints "The app base is relative (./), so flag.js is
721
- linked per HTML file. Pages served by a client-side route fallback cannot load it; build with
722
- an absolute base (--base /path/) if the app has such routes."
725
+ fallback cannot load `flag.js`, so pack prints a warning recommending an absolute base
726
+ (`--base /path/`) when the app has such routes.
723
727
 
724
728
  ### Security
725
729
 
@@ -434,7 +434,7 @@ var path3 = __toESM(require("path"), 1);
434
434
 
435
435
  // src/core/validate.ts
436
436
  var LOCALE_RE = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,4})?$/;
437
- var KEY_RE = /^[a-zA-Z0-9._-]+$/;
437
+ var KEY_RE = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
438
438
  var MAX_LOCALE_LENGTH = 10;
439
439
  var MAX_KEY_LENGTH = 200;
440
440
  var MAX_VALUE_LENGTH = 1e4;
@@ -1331,12 +1331,12 @@ function checkShareCsp(pages, origin) {
1331
1331
  return { level: "ok", title, detail: "scripts from 'self' are allowed" };
1332
1332
  }
1333
1333
  function workerCandidates(root, base) {
1334
- const urls = [];
1334
+ const urls2 = [];
1335
1335
  if (isAnswer(root)) {
1336
1336
  const registered = /serviceWorker\s*\.\s*register\s*\(\s*["'`]([^"'`]+)["'`]/.exec(root.body);
1337
1337
  if (registered) {
1338
1338
  try {
1339
- urls.push(new URL(registered[1], base).href);
1339
+ urls2.push(new URL(registered[1], base).href);
1340
1340
  } catch {
1341
1341
  }
1342
1342
  }
@@ -1346,9 +1346,9 @@ function workerCandidates(root, base) {
1346
1346
  ...names.map((name) => new URL(name, base).href),
1347
1347
  ...names.map((name) => new URL(`/${name}`, base).href)
1348
1348
  ]) {
1349
- if (!urls.includes(url)) urls.push(url);
1349
+ if (!urls2.includes(url)) urls2.push(url);
1350
1350
  }
1351
- return urls;
1351
+ return urls2;
1352
1352
  }
1353
1353
  async function checkServiceWorker(fetchImpl, root, base, origin) {
1354
1354
  const title = "Service worker";
@@ -2552,6 +2552,10 @@ var EXISTING_FLAG_TAG = /<script\b[^>]*\bsrc="[^"]*livelocale\/flag\.js"[^>]*>\s
2552
2552
  var HEAD_OPEN2 = /<head(?=[\s>])[^>]*>/i;
2553
2553
  var HTML_OPEN2 = /<html(?=[\s>])[^>]*>/i;
2554
2554
  var DOCTYPE2 = /<!doctype\b[^>]*>/i;
2555
+ var DOCUMENT_MARKUP = /<(?:!doctype\b|html(?=[\s>])|head(?=[\s>]))/i;
2556
+ function isHtmlDocument(html) {
2557
+ return DOCUMENT_MARKUP.test(html);
2558
+ }
2555
2559
  function escapeAttribute(value) {
2556
2560
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2557
2561
  }
@@ -2790,10 +2794,12 @@ async function packOutput(options) {
2790
2794
  }
2791
2795
  await import_fs5.promises.writeFile(path8.join(shareDir, SHARE_FLAG_FILE), FLAG_SCRIPT, "utf-8");
2792
2796
  const inject = injectHtml === "auto" ? target.kind !== "next" && !(0, import_fs5.existsSync)(path8.join(outDir, "_next")) : injectHtml;
2793
- const htmlInjected = inject ? await findHtmlFiles(outDir, shareDir) : [];
2794
- for (const rel of htmlInjected) {
2797
+ const htmlInjected = [];
2798
+ for (const rel of inject ? await findHtmlFiles(outDir, shareDir) : []) {
2795
2799
  const file = path8.join(outDir, ...rel.split("/"));
2796
2800
  const html = await import_fs5.promises.readFile(file, "utf-8");
2801
+ if (!isHtmlDocument(html)) continue;
2802
+ htmlInjected.push(rel);
2797
2803
  const injected = injectFlagScript(html, flagSrcFor(base, rel));
2798
2804
  if (injected !== html) await import_fs5.promises.writeFile(file, injected, "utf-8");
2799
2805
  }
@@ -2854,6 +2860,55 @@ function parseShareArgs(argv) {
2854
2860
  }
2855
2861
  return args;
2856
2862
  }
2863
+ var PACK_USAGE = "Usage: livelocale pack <outDir> [options]";
2864
+ var BUILD_USAGE = "Usage: livelocale build [--preview] -- <your build command>";
2865
+ function usageError(problem, usage) {
2866
+ return new PackError("USAGE", `${problem}
2867
+ ${usage}`);
2868
+ }
2869
+ function checkPackArgs(args) {
2870
+ if (args.positional.length === 0) {
2871
+ throw usageError("Missing <outDir>: pass the built site folder, for example dist.", PACK_USAGE);
2872
+ }
2873
+ if (args.positional.length > 1) {
2874
+ throw usageError(
2875
+ `Unexpected argument "${args.positional[1]}": pack takes one <outDir>.`,
2876
+ PACK_USAGE
2877
+ );
2878
+ }
2879
+ if (args.out !== void 0) {
2880
+ throw usageError(
2881
+ "--out is not a pack option: pass the built site folder as <outDir>.",
2882
+ PACK_USAGE
2883
+ );
2884
+ }
2885
+ if (args.preview) {
2886
+ throw usageError(
2887
+ "--preview is not a pack option: use livelocale build --preview -- <your build command>.",
2888
+ PACK_USAGE
2889
+ );
2890
+ }
2891
+ if (args.command.length > 0) {
2892
+ throw usageError(
2893
+ "pack does not run a build command: use livelocale build -- <your build command>.",
2894
+ PACK_USAGE
2895
+ );
2896
+ }
2897
+ }
2898
+ function checkBuildArgs(args) {
2899
+ if (args.positional.length > 0) {
2900
+ throw usageError(
2901
+ `Unexpected argument "${args.positional[0]}": put the build command after --.`,
2902
+ BUILD_USAGE
2903
+ );
2904
+ }
2905
+ if (!args.inject) {
2906
+ throw usageError(
2907
+ "--no-inject is not a build option: livelocale build links flag.js itself (never on Next.js).",
2908
+ BUILD_USAGE
2909
+ );
2910
+ }
2911
+ }
2857
2912
  var WINDOWS_NEEDS_QUOTES = /[\s"&|<>^()]/;
2858
2913
  function quoteWindowsArg(arg) {
2859
2914
  if (arg !== "" && !WINDOWS_NEEDS_QUOTES.test(arg)) return arg;
@@ -2886,7 +2941,6 @@ var spawnInherit = (command, args, { cwd, env }) => new Promise((resolve16, reje
2886
2941
  });
2887
2942
  child.on("close", (code) => resolve16(code ?? 1));
2888
2943
  });
2889
- var BUILD_USAGE = "Usage: livelocale build [--preview] -- <your build command>";
2890
2944
  var NEXT_PREVIEW_WARNING = "Next.js pages have no watermarks without --preview; the Studio will open but nothing is editable.";
2891
2945
  var NEXT_GITIGNORE_WARNING = "Add public/livelocale/ to .gitignore; it is generated on every build.";
2892
2946
  var FRESH_SLACK_MS = 2e3;
@@ -2925,6 +2979,12 @@ async function runBuildCommand(options) {
2925
2979
  const childEnv = preview ? { ...env, LIVELOCALE: "preview", NEXT_PUBLIC_LIVELOCALE: "preview" } : env;
2926
2980
  const runBuild = () => run3(command[0], command.slice(1), { cwd: projectDir, env: childEnv });
2927
2981
  if (target.kind === "next") {
2982
+ if (outDir !== void 0) {
2983
+ throw new PackError(
2984
+ "USAGE",
2985
+ "--out is not used for Next.js; the share folder goes to public/"
2986
+ );
2987
+ }
2928
2988
  const blocking = target.staticExport ? findExportBlockingRoute(projectDir) : null;
2929
2989
  if (blocking !== null) {
2930
2990
  throw new PackError(
@@ -3134,6 +3194,13 @@ var URL_PATTERN = /\b(?:https?:\/\/|www\.)[^\s<>"')]+/gi;
3134
3194
  var BIDI_CONTROL_PATTERN = /[\u202A-\u202E\u2066-\u2069]/g;
3135
3195
  var tagNames = (text) => Array.from(text.matchAll(TAG_PATTERN), (m) => m[1].toLowerCase());
3136
3196
  var allMatches = (pattern, text) => text.match(pattern) ?? [];
3197
+ var URL_PARTS_PATTERN = /^((?:https?:\/\/|www\.)[^/?#]*)(.*)$/i;
3198
+ var normalizeUrl = (url) => {
3199
+ const trimmed = url.replace(/[.,;:!?]+$/, "");
3200
+ const parts = URL_PARTS_PATTERN.exec(trimmed);
3201
+ return parts ? parts[1].toLowerCase() + parts[2] : trimmed;
3202
+ };
3203
+ var urls = (text) => allMatches(URL_PATTERN, text).map(normalizeUrl);
3137
3204
  var hasNew = (known, found) => {
3138
3205
  const set = new Set(known);
3139
3206
  return found.some((item) => !set.has(item));
@@ -3145,7 +3212,7 @@ function detectRiskFlags(reference, value) {
3145
3212
  if (SCRIPT_LINK_PATTERNS.some((pattern) => pattern.test(value) && !pattern.test(ref))) {
3146
3213
  flags.push("javascript-url");
3147
3214
  }
3148
- if (hasNew(allMatches(URL_PATTERN, ref), allMatches(URL_PATTERN, value))) flags.push("new-url");
3215
+ if (hasNew(urls(ref), urls(value))) flags.push("new-url");
3149
3216
  if (hasNew(allMatches(BIDI_CONTROL_PATTERN, ref), allMatches(BIDI_CONTROL_PATTERN, value))) {
3150
3217
  flags.push("bidi-control");
3151
3218
  }
@@ -3504,13 +3571,39 @@ function buildFileChanges(localesDir, plan, remote, baseFiles) {
3504
3571
  }
3505
3572
  return changes;
3506
3573
  }
3574
+ var MAX_FALLBACK_SUFFIX = 100;
3575
+ async function findOpenFallbackPull(rest, baseName, base) {
3576
+ const fallback = new RegExp(
3577
+ `^${baseName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-\\d+(?:-\\d+)?$`
3578
+ );
3579
+ const res = await rest.api(
3580
+ "GET",
3581
+ "/pulls",
3582
+ { query: { base, state: "open", per_page: "100" } }
3583
+ );
3584
+ for (const pull of res.data ?? []) {
3585
+ const ref = pull.head?.ref ?? "";
3586
+ const headRepo = pull.head?.repo?.full_name?.toLowerCase();
3587
+ if (headRepo !== rest.repo.toLowerCase() || !fallback.test(ref)) continue;
3588
+ const head = await rest.refSha(ref);
3589
+ if (head !== null) return { branch: ref, head };
3590
+ }
3591
+ return null;
3592
+ }
3507
3593
  async function prepareBranch(rest, baseName, base, targetSha, runId) {
3508
3594
  const head = await rest.refSha(baseName);
3509
3595
  if (head === null) return { branch: baseName, parentSha: targetSha, mode: "new" };
3510
3596
  if (await rest.findOpenPullRequest(baseName, base)) {
3511
3597
  return { branch: baseName, parentSha: head, mode: "append" };
3512
3598
  }
3513
- return { branch: `${baseName}-${runId}`, parentSha: targetSha, mode: "new" };
3599
+ const open = await findOpenFallbackPull(rest, baseName, base);
3600
+ if (open) return { branch: open.branch, parentSha: open.head, mode: "append" };
3601
+ const fallback = `${baseName}-${runId}`;
3602
+ for (let n = 1; n <= MAX_FALLBACK_SUFFIX; n++) {
3603
+ const branch = n === 1 ? fallback : `${fallback}-${n}`;
3604
+ if (await rest.refSha(branch) === null) return { branch, parentSha: targetSha, mode: "new" };
3605
+ }
3606
+ throw new Error(`[livelocale] No free branch name after ${fallback}-${MAX_FALLBACK_SUFFIX}.`);
3514
3607
  }
3515
3608
  async function commitFiles(rest, input) {
3516
3609
  const { branch, parentSha, mode } = input.branchPlan;
@@ -3555,7 +3648,7 @@ function noreplyAuthor(actor) {
3555
3648
  // src/codemod/apply/github-trigger.ts
3556
3649
  var APPLY_COMMAND_RE = /^\/livelocale\s+apply(\s+--force)?\s*$/;
3557
3650
  var COMMAND_PREFIX = "/livelocale";
3558
- var ISSUE_BRANCH_RE = /^livelocale\/issue-(\d+)(?:-\d+)?$/;
3651
+ var ISSUE_BRANCH_RE = /^livelocale\/issue-(\d+)(?:-\d+){0,2}$/;
3559
3652
  var TRUSTED_ASSOCIATIONS = /* @__PURE__ */ new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
3560
3653
  var TRUSTED_PERMISSIONS = /* @__PURE__ */ new Set(["admin", "write"]);
3561
3654
  var TRUSTED_ROLES = /* @__PURE__ */ new Set(["admin", "maintain", "write"]);
@@ -3599,7 +3692,8 @@ function parseTrigger(eventName, event) {
3599
3692
  if (event?.action !== "created") return ignore("comment not created");
3600
3693
  const issue = event.issue;
3601
3694
  const comment = event.comment;
3602
- if (issue?.pull_request) return ignore("comment on a pull request");
3695
+ if (!issue) return ignore("comment without an issue");
3696
+ if (issue.pull_request) return ignore("comment on a pull request");
3603
3697
  if (comment?.user?.type === "Bot") return ignore("comment by a bot");
3604
3698
  const body = textOf(comment?.body);
3605
3699
  const firstLine = body.split(/\r\n|\r|\n/)[0] ?? "";
@@ -3669,7 +3763,9 @@ async function isTrustedActor(rest, login, association) {
3669
3763
  function chooseTargetBranch(requested, allowList, defaultBranch) {
3670
3764
  if (!requested) return { branch: defaultBranch, reason: "default" };
3671
3765
  const allowed = allowList.length > 0 ? allowList : [defaultBranch];
3672
- if (allowed.includes(requested)) return { branch: requested, reason: "link" };
3766
+ if (requested === defaultBranch || allowed.includes(requested)) {
3767
+ return { branch: requested, reason: "link" };
3768
+ }
3673
3769
  return { branch: defaultBranch, reason: "not-allowed" };
3674
3770
  }
3675
3771
  function branchBaseName(t, runId) {
@@ -3692,7 +3788,7 @@ var STATUS_LABELS = {
3692
3788
  "needs-force": "needs --force",
3693
3789
  invalid: "rejected"
3694
3790
  };
3695
- var BIDI_CONTROL_RE = /[‪-‮⁦-⁩]/g;
3791
+ var BIDI_CONTROL_RE = /[\u202a-\u202e\u2066-\u2069]/g;
3696
3792
  var NEWLINE_RE = /\r\n|\r|\n/g;
3697
3793
  var SPAN_CONTROL_RE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
3698
3794
  var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/g;
@@ -4012,8 +4108,13 @@ async function runGitHubEventApply(options) {
4012
4108
  try {
4013
4109
  return await applyLink(options, rest, trigger, { repo, runId, runUrl, server, log, report });
4014
4110
  } catch (err) {
4015
- if (err instanceof ExpectedFailure) return report({ kind: "error", message: err.message });
4016
- await report({ kind: "error", message: errorMessage(err) });
4111
+ const message = err instanceof ExpectedFailure ? err.message : errorMessage(err);
4112
+ try {
4113
+ const result = await report({ kind: "error", message });
4114
+ if (err instanceof ExpectedFailure) return result;
4115
+ } catch (reportErr) {
4116
+ log(`Could not post the error comment: ${errorMessage(reportErr)}`);
4117
+ }
4017
4118
  throw err;
4018
4119
  }
4019
4120
  }
@@ -4938,6 +5039,7 @@ async function runLocalApply(options) {
4938
5039
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
4939
5040
  const branch = await pickBranchName(git, root, utcStamp(now()));
4940
5041
  await git(["switch", "-c", branch], root);
5042
+ let committed = false;
4941
5043
  try {
4942
5044
  const written = await writeEdits(mutator, plan, targets, localesDir, localesPath);
4943
5045
  log(renderTerminalSummary(plan));
@@ -4955,6 +5057,7 @@ async function runLocalApply(options) {
4955
5057
  checkSummary = `Check could not run: ${errorMessage2(err)}`;
4956
5058
  }
4957
5059
  await new GitClient().commit(localesDir, renderCommitMessage({ plan, by: set.by }));
5060
+ committed = true;
4958
5061
  await git(["push", "-u", "origin", branch], root, { inherit: true });
4959
5062
  const remote = (await tryRun(() => git(["remote", "get-url", "origin"], root)))?.trim();
4960
5063
  const slug = remote ? GITHUB_REMOTE_RE.exec(remote)?.[1] : void 0;
@@ -4985,7 +5088,9 @@ async function runLocalApply(options) {
4985
5088
  await git(["switch", original], root);
4986
5089
  return { plan, written, branch, prUrl, exitCode };
4987
5090
  } catch (err) {
4988
- log(`You are on ${branch}; your changes are committed there.`);
5091
+ log(
5092
+ committed ? `You are on ${branch}; your changes are committed there.` : `You are on ${branch}; the edits are written but not committed.`
5093
+ );
4989
5094
  throw err;
4990
5095
  }
4991
5096
  }
@@ -5193,6 +5298,37 @@ function renderWorkflow(o) {
5193
5298
  ""
5194
5299
  ].join("\n");
5195
5300
  }
5301
+ function parseInitShareArgs(argv) {
5302
+ const args = {
5303
+ dir: ".",
5304
+ localesPath: void 0,
5305
+ sourceLocale: void 0,
5306
+ branches: void 0,
5307
+ overwrite: false
5308
+ };
5309
+ const valueAt = (i) => {
5310
+ const value = argv[i + 1];
5311
+ if (value === void 0 || value.startsWith("--")) {
5312
+ throw new Error(`${argv[i]} needs a value.`);
5313
+ }
5314
+ return value;
5315
+ };
5316
+ for (let i = 0; i < argv.length; i++) {
5317
+ const arg = argv[i];
5318
+ if (arg === "--dir") {
5319
+ args.dir = valueAt(i++);
5320
+ } else if (arg === "--locales") {
5321
+ args.localesPath = valueAt(i++);
5322
+ } else if (arg === "--source") {
5323
+ args.sourceLocale = valueAt(i++);
5324
+ } else if (arg === "--branches") {
5325
+ args.branches = valueAt(i++).split(",").map((b) => b.trim()).filter(Boolean);
5326
+ } else if (arg === "--overwrite") {
5327
+ args.overwrite = true;
5328
+ }
5329
+ }
5330
+ return args;
5331
+ }
5196
5332
  function toPosix2(p) {
5197
5333
  return p.split(path17.sep).join("/");
5198
5334
  }
@@ -5229,14 +5365,17 @@ function findLocalesPath(projectDir) {
5229
5365
  if (!found) throw new Error("Pass --locales <path>.");
5230
5366
  return found;
5231
5367
  }
5368
+ var REMOTE_HEAD = /^ref: refs\/heads\/(\S+)\s+HEAD$/m;
5232
5369
  async function defaultBranchOf(git, cwd) {
5233
5370
  const ref = await git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], cwd);
5234
5371
  if (ref?.startsWith("origin/") && ref.length > "origin/".length) {
5235
- return ref.slice("origin/".length);
5372
+ return { branch: ref.slice("origin/".length), assumed: false };
5236
5373
  }
5374
+ const remote = await git(["ls-remote", "--symref", "origin", "HEAD"], cwd);
5375
+ const remoteHead = REMOTE_HEAD.exec(remote ?? "");
5376
+ if (remoteHead) return { branch: remoteHead[1], assumed: false };
5237
5377
  const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
5238
- if (head && head !== "HEAD") return head;
5239
- return "main";
5378
+ return { branch: head && head !== "HEAD" ? head : "main", assumed: true };
5240
5379
  }
5241
5380
  function planWrite(projectDir, rel, content) {
5242
5381
  const current = readText3(path17.join(projectDir, rel));
@@ -5261,7 +5400,7 @@ function planViteConfig(c) {
5261
5400
  c.notes.push(addNote);
5262
5401
  return [{ path: config, action: "skipped", reason: "liveLocaleVite(...) has no { options }" }];
5263
5402
  }
5264
- const source2 = c.sourceLocale === "en" ? "" : `sourceLocale: '${c.sourceLocale}', `;
5403
+ const source2 = c.sourceLocale === "en" || /\bsourceLocale\s*:/.test(source) ? "" : `sourceLocale: '${c.sourceLocale}', `;
5265
5404
  const next = source.replace(call, () => `liveLocaleVite({ share: true, ${source2}`);
5266
5405
  return [{ path: config, action: "update", content: next }];
5267
5406
  }
@@ -5376,7 +5515,12 @@ async function planInitShare(o) {
5376
5515
  const appRel = toPosix2(path17.relative(repoRoot, projectDir));
5377
5516
  const dir = appRel === "" ? void 0 : appRel;
5378
5517
  const repoLocales = dir === void 0 ? localesPath : `${dir}/${localesPath}`;
5379
- const defaultBranch = await defaultBranchOf(git, projectDir);
5518
+ const { branch: defaultBranch, assumed } = await defaultBranchOf(git, projectDir);
5519
+ if (assumed) {
5520
+ notes.push(
5521
+ `Could not read the default branch from origin; assumed \`${defaultBranch}\`. Pass --branches <branch> if that is wrong.`
5522
+ );
5523
+ }
5380
5524
  const branches = o.branches ?? [defaultBranch];
5381
5525
  const workflow = renderWorkflow({
5382
5526
  version: o.version,
@@ -5403,11 +5547,12 @@ async function planInitShare(o) {
5403
5547
  return { stack, files, notes };
5404
5548
  }
5405
5549
  function writePlannedFiles(projectDir, files) {
5550
+ const root = realDir(projectDir);
5406
5551
  for (const file of files) {
5407
5552
  if (file.action !== "create" && file.action !== "update" || file.content === void 0) {
5408
5553
  continue;
5409
5554
  }
5410
- const target = path17.resolve(projectDir, file.path);
5555
+ const target = path17.resolve(root, file.path);
5411
5556
  fs11.mkdirSync(path17.dirname(target), { recursive: true });
5412
5557
  fs11.writeFileSync(target, file.content, "utf-8");
5413
5558
  }
@@ -5881,7 +6026,6 @@ async function runCheckCommand(args) {
5881
6026
  warns > 0 ? `\u2705 No problems, ${warns} warning(s) worth a look.` : `\u2705 Translations look good.`
5882
6027
  );
5883
6028
  }
5884
- var PACK_USAGE = "Usage: livelocale pack <outDir> [options]";
5885
6029
  function printPackSummary(result, projectDir, warnings) {
5886
6030
  for (const warning of warnings) console.log(`\u26A0\uFE0F ${warning}`);
5887
6031
  const outRel = path18.relative(projectDir, path18.dirname(result.shareDir)).split(path18.sep).join("/") || ".";
@@ -5893,9 +6037,7 @@ function printPackSummary(result, projectDir, warnings) {
5893
6037
  async function runPackCommand(args) {
5894
6038
  try {
5895
6039
  const options = parseShareArgs(args.slice(1));
5896
- if (options.positional.length !== 1 || options.out !== void 0 || options.preview || options.command.length > 0) {
5897
- throw new PackError("USAGE", PACK_USAGE);
5898
- }
6040
+ checkPackArgs(options);
5899
6041
  const projectDir = path18.resolve(options.dir);
5900
6042
  const result = await packOutput({
5901
6043
  projectDir,
@@ -5916,9 +6058,7 @@ async function runPackCommand(args) {
5916
6058
  async function runBuildCli(args) {
5917
6059
  try {
5918
6060
  const options = parseShareArgs(args.slice(1));
5919
- if (options.positional.length > 0 || !options.inject) {
5920
- throw new PackError("USAGE", BUILD_USAGE);
5921
- }
6061
+ checkBuildArgs(options);
5922
6062
  const projectDir = path18.resolve(options.dir);
5923
6063
  const result = await runBuildCommand({
5924
6064
  projectDir,
@@ -5943,25 +6083,15 @@ async function runBuildCli(args) {
5943
6083
  }
5944
6084
  }
5945
6085
  async function runInitShareCommand(args) {
5946
- let dir = ".";
5947
- let localesPath;
5948
- let sourceLocale;
5949
- let branches;
5950
- let overwrite = false;
5951
- for (let i = 1; i < args.length; i++) {
5952
- const arg = args[i];
5953
- if (arg === "--dir") {
5954
- dir = args[++i] || ".";
5955
- } else if (arg === "--locales") {
5956
- localesPath = args[++i];
5957
- } else if (arg === "--source") {
5958
- sourceLocale = args[++i];
5959
- } else if (arg === "--branches") {
5960
- branches = (args[++i] ?? "").split(",").map((b) => b.trim()).filter(Boolean);
5961
- } else if (arg === "--overwrite") {
5962
- overwrite = true;
5963
- }
6086
+ let parsed;
6087
+ try {
6088
+ parsed = parseInitShareArgs(args.slice(1));
6089
+ } catch (err) {
6090
+ console.error(`\u274C ${err.message}`);
6091
+ process.exitCode = 1;
6092
+ return;
5964
6093
  }
6094
+ const { dir, localesPath, sourceLocale, branches, overwrite } = parsed;
5965
6095
  const projectDir = path18.resolve(dir);
5966
6096
  if (!fs12.existsSync(projectDir)) {
5967
6097
  console.error(`Error: Directory "${dir}" does not exist.`);