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 +30 -26
- package/dist/codemod/index.cjs +178 -48
- package/dist/codemod/index.cjs.map +1 -1
- package/dist/codemod/index.js +178 -48
- package/dist/codemod/index.js.map +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/studio/assets/index-167fHSh2.js +14 -0
- package/dist/studio/assets/index-GgcTqdBQ.css +1 -0
- package/dist/studio/index.html +2 -2
- package/dist/vite/index.cjs +9 -3
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.js +9 -3
- package/dist/vite/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/studio/assets/index-FFC_lDHv.css +0 -1
- package/dist/studio/assets/index-yI6iF4xN.js +0 -14
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.
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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 `-`
|
|
662
|
-
the link in (`npx livelocale apply - < changes.txt`). See [`apply`](#apply) for every
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
721
|
-
|
|
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
|
|
package/dist/codemod/index.cjs
CHANGED
|
@@ -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 = /^[
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
1349
|
+
if (!urls2.includes(url)) urls2.push(url);
|
|
1350
1350
|
}
|
|
1351
|
-
return
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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 =
|
|
2794
|
-
for (const rel of
|
|
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(
|
|
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
|
-
|
|
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
|
|
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))
|
|
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 = /[
|
|
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
|
-
|
|
4016
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
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.`);
|