@kirchdev/duxt 0.3.4 → 0.3.5-dev-main.44-5337cc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/app/app.vue +17 -11
- package/app/assets/css/duxt.css +47 -6
- package/app/components/DuxtAnnouncements.vue +117 -0
- package/app/components/DuxtBrunoEntries.vue +62 -0
- package/app/components/DuxtCodeBlock.vue +18 -34
- package/app/components/DuxtCodeToolbar.vue +14 -0
- package/app/components/DuxtCopyButton.vue +33 -0
- package/app/components/DuxtCopyPage.vue +32 -30
- package/app/components/DuxtFooter.vue +39 -8
- package/app/components/DuxtHeader.vue +107 -48
- package/app/components/DuxtLandingProse.vue +1 -1
- package/app/components/DuxtLinkLabel.vue +36 -0
- package/app/components/DuxtLiveWindow.vue +8 -8
- package/app/components/DuxtLocale.vue +10 -1
- package/app/components/DuxtNavigation.vue +2 -2
- package/app/components/DuxtOpenApiClient.vue +285 -90
- package/app/components/DuxtOpenApiMedia.vue +30 -18
- package/app/components/DuxtOpenApiSchema.vue +10 -7
- package/app/components/DuxtPageFeedback.vue +7 -0
- package/app/components/DuxtPageInfo.vue +15 -12
- package/app/components/DuxtPageNav.vue +41 -27
- package/app/components/DuxtPreviewSkeleton.vue +4 -4
- package/app/components/DuxtProgress.vue +2 -14
- package/app/components/DuxtSearch.vue +230 -90
- package/app/components/DuxtSearchInput.vue +70 -0
- package/app/components/DuxtSections.vue +11 -1
- package/app/components/DuxtSegmented.vue +64 -0
- package/app/components/DuxtShortcuts.vue +16 -11
- package/app/components/DuxtShortcutsTrigger.vue +49 -0
- package/app/components/DuxtSkipLink.vue +1 -1
- package/app/components/DuxtToc.vue +16 -10
- package/app/components/DuxtTranslationBanner.vue +1 -1
- package/app/components/DuxtVersion.vue +53 -12
- package/app/components/DuxtVersionBanner.vue +5 -5
- package/app/components/content/AccordionItem.vue +2 -2
- package/app/components/content/BrunoOverview.vue +260 -0
- package/app/components/content/BrunoRequest.vue +141 -0
- package/app/components/content/BrunoRequests.vue +104 -0
- package/app/components/content/Callout.vue +5 -5
- package/app/components/content/ChangelogReleases.vue +161 -40
- package/app/components/content/CodeGroup.vue +41 -37
- package/app/components/content/Field.vue +1 -1
- package/app/components/content/FileTree.vue +1 -1
- package/app/components/content/OpenApiOperation.vue +4 -4
- package/app/components/content/OpenApiOperations.vue +1 -1
- package/app/components/content/OpenApiOverview.vue +1 -1
- package/app/components/content/PackageManagers.vue +42 -58
- package/app/components/content/PageCards.vue +7 -4
- package/app/components/content/Preview.vue +15 -39
- package/app/components/content/ProseImg.vue +596 -49
- package/app/components/content/Since.vue +1 -1
- package/app/components/content/Steps.vue +6 -4
- package/app/components/content/TabGroup.vue +13 -12
- package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +1 -1
- package/app/components/ui/command/CommandShortcut.vue +1 -1
- package/app/components/ui/dialog/DialogContent.vue +2 -2
- package/app/components/ui/dialog/DialogHeader.vue +1 -1
- package/app/components/ui/dialog/DialogScrollContent.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +2 -2
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +2 -2
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +2 -2
- package/app/components/ui/navigation-menu/NavigationMenuContent.vue +1 -1
- package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +1 -1
- package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +1 -1
- package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/app/components/ui/select/SelectItem.vue +2 -2
- package/app/components/ui/sheet/SheetContent.vue +29 -3
- package/app/components/ui/sidebar/Sidebar.vue +28 -4
- package/app/components/ui/sidebar/SidebarGroupAction.vue +1 -1
- package/app/components/ui/sidebar/SidebarInset.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuAction.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuBadge.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuSub.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuSubButton.vue +1 -1
- package/app/components/ui/sidebar/SidebarProvider.vue +7 -16
- package/app/components/ui/sidebar/index.ts +10 -2
- package/app/components/ui/sidebar/utils.ts +0 -1
- package/app/components/ui/sonner/Sonner.vue +18 -0
- package/app/components/ui/tabs/TabsList.vue +13 -3
- package/app/components/ui/tabs/TabsTrigger.vue +24 -3
- package/app/components/ui/tooltip/TooltipContent.vue +2 -2
- package/app/composables/useActiveHeading.ts +3 -25
- package/app/composables/useDuxtAnalytics.ts +50 -0
- package/app/composables/useDuxtAnimatedHeight.ts +8 -38
- package/app/composables/useDuxtAnnouncements.ts +153 -0
- package/app/composables/useDuxtCopy.ts +51 -0
- package/app/composables/useDuxtDirection.ts +23 -0
- package/app/composables/useDuxtNavigation.ts +28 -8
- package/app/composables/useDuxtPath.ts +3 -5
- package/app/composables/useDuxtSearch.ts +87 -35
- package/app/composables/useDuxtSection.ts +15 -1
- package/app/composables/useDuxtSectionRow.ts +6 -5
- package/app/composables/useDuxtShortcuts.ts +319 -20
- package/app/composables/useDuxtVersion.ts +57 -6
- package/app/composables/useDuxtViewportMeasure.ts +37 -0
- package/app/composables/useFuzzySearch.ts +9 -4
- package/app/composables/useRecentPages.ts +14 -17
- package/app/error.vue +112 -74
- package/app/layouts/changelog.vue +9 -1
- package/app/layouts/default.vue +9 -0
- package/app/layouts/docs.vue +9 -1
- package/app/layouts/reference.vue +9 -1
- package/app/middleware/duxt-section-layout.global.ts +1 -3
- package/app/pages/[...slug].vue +146 -35
- package/app/pages/index.vue +15 -23
- package/app/types/duxt.d.ts +312 -4
- package/app/utils/analytics.ts +91 -0
- package/app/utils/announcements.ts +205 -0
- package/app/utils/changelog.ts +0 -28
- package/app/utils/code-highlight.ts +3 -1
- package/app/utils/contributor-avatar.ts +27 -0
- package/app/utils/direction.ts +43 -0
- package/app/utils/duxt-config.ts +43 -0
- package/app/utils/duxt-text.ts +10 -0
- package/app/utils/format-date.ts +28 -0
- package/app/utils/generated-sections.ts +68 -12
- package/app/utils/generated-toc.ts +14 -7
- package/app/utils/locale-path.ts +43 -0
- package/app/utils/navigation-tree.ts +51 -1
- package/app/utils/nearest-page.ts +8 -0
- package/app/utils/openapi.ts +16 -11
- package/app/utils/page-controls.ts +215 -0
- package/app/utils/pill.ts +61 -0
- package/app/utils/prose-image.ts +128 -0
- package/app/utils/search-context.ts +110 -0
- package/app/utils/search-display.ts +248 -0
- package/app/utils/section-scope.ts +65 -4
- package/app/utils/stored-json.ts +31 -0
- package/app/utils/version-choices.ts +6 -13
- package/app/utils/version-paths.ts +21 -0
- package/bin/duxt-cache-key.mjs +61 -0
- package/bin/duxt-og-cache.mjs +58 -0
- package/bin/{duxt-report.mjs → duxt.mjs} +13 -7
- package/bruno-model.ts +169 -0
- package/bruno-parse.ts +656 -0
- package/bruno-zip.ts +195 -0
- package/cli.ts +240 -0
- package/duxt-app-config.ts +18 -1
- package/frontmatter.ts +49 -0
- package/git-contributors.ts +322 -0
- package/i18n/locales/de/duxt/announcement.json +7 -0
- package/i18n/locales/de/duxt/bruno.json +25 -0
- package/i18n/locales/de/duxt/changelog.json +1 -1
- package/i18n/locales/de/duxt/code.json +2 -1
- package/i18n/locales/de/duxt/page.json +9 -0
- package/i18n/locales/de/duxt/shortcuts.json +1 -0
- package/i18n/locales/de/duxt/theme.json +2 -1
- package/i18n/locales/en/duxt/announcement.json +7 -0
- package/i18n/locales/en/duxt/bruno.json +25 -0
- package/i18n/locales/en/duxt/changelog.json +1 -1
- package/i18n/locales/en/duxt/code.json +2 -1
- package/i18n/locales/en/duxt/page.json +9 -0
- package/i18n/locales/en/duxt/shortcuts.json +1 -0
- package/i18n/locales/en/duxt/theme.json +2 -1
- package/i18n/locales/es/duxt/announcement.json +7 -0
- package/i18n/locales/es/duxt/bruno.json +25 -0
- package/i18n/locales/es/duxt/changelog.json +1 -1
- package/i18n/locales/es/duxt/code.json +2 -1
- package/i18n/locales/es/duxt/page.json +9 -0
- package/i18n/locales/es/duxt/shortcuts.json +1 -0
- package/i18n/locales/es/duxt/theme.json +2 -1
- package/i18n/locales/fr/duxt/announcement.json +7 -0
- package/i18n/locales/fr/duxt/bruno.json +25 -0
- package/i18n/locales/fr/duxt/changelog.json +1 -1
- package/i18n/locales/fr/duxt/code.json +2 -1
- package/i18n/locales/fr/duxt/page.json +9 -0
- package/i18n/locales/fr/duxt/shortcuts.json +1 -0
- package/i18n/locales/fr/duxt/theme.json +2 -1
- package/i18n/locales/pt/duxt/announcement.json +7 -0
- package/i18n/locales/pt/duxt/bruno.json +25 -0
- package/i18n/locales/pt/duxt/changelog.json +1 -1
- package/i18n/locales/pt/duxt/code.json +2 -1
- package/i18n/locales/pt/duxt/page.json +9 -0
- package/i18n/locales/pt/duxt/shortcuts.json +1 -0
- package/i18n/locales/pt/duxt/theme.json +2 -1
- package/modules/bruno.ts +118 -0
- package/modules/config.ts +26 -0
- package/modules/devtools.ts +2 -3
- package/modules/git-meta.ts +16 -47
- package/modules/search-records.ts +132 -0
- package/nuxt.config.ts +81 -7
- package/og-image-cache.ts +742 -0
- package/package.json +42 -10
- package/public/devtools/checks.html +2 -2
- package/public/devtools/config.html +59 -4
- package/public/devtools/pages.html +8 -1
- package/public/devtools/paths.html +1 -1
- package/public/devtools/sources.html +2 -2
- package/public/devtools/versions.html +3 -0
- package/report.ts +28 -19
- package/search-records.ts +380 -0
- package/section-input.ts +142 -0
- package/section-markdown.ts +67 -0
- package/section-reports.ts +10 -4
- package/sections-bruno.ts +571 -0
- package/sections-changelog.ts +76 -43
- package/sections-openapi.ts +12 -83
- package/sections-resolve.ts +174 -8
- package/sections.ts +33 -10
- package/server/devtools/shell.ts +1 -1
- package/server/mcp/tools/list-pages.ts +77 -36
- package/server/mcp/tools/list-versions.ts +84 -0
- package/server/mcp/tools/read-page.ts +80 -26
- package/server/mcp/tools/search-docs.ts +87 -53
- package/server/middleware/raw-markdown.ts +1 -1
- package/server/routes/llms-full.txt.get.ts +6 -8
- package/server/routes/llms.txt.get.ts +1 -1
- package/server/utils/duxt-server-text.ts +0 -13
- package/server/utils/llms-pages.ts +24 -9
- package/server/utils/mcp-docs.ts +577 -0
- package/sources-cache.ts +376 -0
- package/sources-git.ts +186 -31
- package/sources-resolve.ts +59 -1
- package/sources.ts +122 -13
- package/tfplugindocs.ts +158 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Who wrote something, read out of git.
|
|
5
|
+
*
|
|
6
|
+
* TWO SURFACES ASK THIS, and they ask it about different things. A page asks
|
|
7
|
+
* who touched the file it was parsed from — `modules/git-meta.ts`, indexed by
|
|
8
|
+
* path. A release asks who wrote the commits it carries — the release history,
|
|
9
|
+
* indexed by tag. What they share is the part that decides WHO A PERSON IS, and
|
|
10
|
+
* that is the whole reason this file exists rather than two copies of it: an
|
|
11
|
+
* identity computed twice is one reader shown as two people on one page and as
|
|
12
|
+
* one on the next.
|
|
13
|
+
*
|
|
14
|
+
* Node-only, and a root module rather than something under `modules/`, for the
|
|
15
|
+
* reason `repository-root.ts` is one: `sections-changelog.ts` reaches it out of
|
|
16
|
+
* the Content config, and a Nuxt module drags a type graph that config has no
|
|
17
|
+
* business resolving.
|
|
18
|
+
*
|
|
19
|
+
* NO EMAIL EVER LEAVES THIS FILE. It is the identity every answer below is
|
|
20
|
+
* keyed by, and it is the one field a commit carries that a published page has
|
|
21
|
+
* no business republishing: an address in a commit object is between a
|
|
22
|
+
* repository and whoever clones it, where the same address on a prerendered,
|
|
23
|
+
* crawled, indexed page is a harvesting target the person never agreed to.
|
|
24
|
+
* `DuxtContributor` is therefore a name, a count and — where git itself
|
|
25
|
+
* carries one — a GitHub handle, which is already public by construction.
|
|
26
|
+
*/
|
|
27
|
+
export interface DuxtContributor {
|
|
28
|
+
name: string;
|
|
29
|
+
commits: number;
|
|
30
|
+
username?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The fields either caller supplies about one commit. */
|
|
34
|
+
export interface DuxtCommitAuthor {
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* One entry per person, most commits first.
|
|
41
|
+
*
|
|
42
|
+
* Identity is the email, not the name — the same person commits as "Titus
|
|
43
|
+
* Kirch" and as "titus" and would otherwise appear twice. The GitHub username
|
|
44
|
+
* is read out of a noreply address, which is the only place git actually
|
|
45
|
+
* carries one; without it there is a name and no avatar, which is the truth.
|
|
46
|
+
*/
|
|
47
|
+
export function contributorsOf(commits: DuxtCommitAuthor[]): DuxtContributor[] {
|
|
48
|
+
const people = new Map<string, DuxtContributor>();
|
|
49
|
+
|
|
50
|
+
for (const commit of commits) {
|
|
51
|
+
const key = commit.email.toLowerCase();
|
|
52
|
+
const existing = people.get(key);
|
|
53
|
+
|
|
54
|
+
if (existing) {
|
|
55
|
+
existing.commits += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
people.set(key, {
|
|
60
|
+
name: commit.name,
|
|
61
|
+
commits: 1,
|
|
62
|
+
username: githubUsername(commit.email)
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return [...people.values()].sort((a, b) => b.commits - a.commits);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** `1234567+octocat@users.noreply.github.com` becomes `octocat`. */
|
|
70
|
+
export function githubUsername(email: string): string | undefined {
|
|
71
|
+
const match = /^(?:\d+\+)?([^@]+)@users\.noreply\.github\.com$/i.exec(
|
|
72
|
+
email.trim()
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return match?.[1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* An address GitHub itself issued to an APP rather than to a person.
|
|
80
|
+
*
|
|
81
|
+
* READ, NEVER GUESSED. GitHub writes `<id>+<app>[bot]@users.noreply.github.com`
|
|
82
|
+
* for every app installation, so the marker is the forge's own and not a
|
|
83
|
+
* taxonomy of ours — the same standard the changelog's group names are held to.
|
|
84
|
+
* The EMAIL is tested rather than the display name, because the email is
|
|
85
|
+
* already the identity above and a person may call themselves anything.
|
|
86
|
+
*
|
|
87
|
+
* Read by the RELEASE history and not by the page history, which is a scope
|
|
88
|
+
* line rather than an inconsistency: the issue this answers asks a release to
|
|
89
|
+
* name the people who wrote it, and this repository's own releases would
|
|
90
|
+
* otherwise each open with the bot that cut them. Whether "last touched by"
|
|
91
|
+
* should read the same way is a question about another surface.
|
|
92
|
+
*/
|
|
93
|
+
export function isGitHubApp(author: DuxtCommitAuthor): boolean {
|
|
94
|
+
return /\[bot\]@users\.noreply\.github\.com$/i.test(author.email.trim());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The unit separator, not a comma or a pipe.
|
|
99
|
+
*
|
|
100
|
+
* An author name may hold anything a person is called and a decoration list is
|
|
101
|
+
* comma-separated prose; the one byte that cannot appear in either is one git
|
|
102
|
+
* will emit for us with `%x1f`.
|
|
103
|
+
*/
|
|
104
|
+
const SEPARATOR = '\u001f';
|
|
105
|
+
|
|
106
|
+
/** The record separator, marking where one commit's line begins. */
|
|
107
|
+
const RECORD = '\u0001';
|
|
108
|
+
|
|
109
|
+
/** The fields one record carries, in order. */
|
|
110
|
+
const FORMAT = `${RECORD}%P${SEPARATOR}%an${SEPARATOR}%ae${SEPARATOR}%D`;
|
|
111
|
+
|
|
112
|
+
/** What git is asked, and the whole of why each flag is there. */
|
|
113
|
+
const ARGUMENTS = [
|
|
114
|
+
'log',
|
|
115
|
+
// FROM THE TAGS, NOT FROM `HEAD`. A release tag is cut on the release branch,
|
|
116
|
+
// so on an integration branch — which is what every pull request builds — all
|
|
117
|
+
// but the oldest are unreachable and the history comes back empty. Walking
|
|
118
|
+
// the tags also excludes, for free, the commits no release carries yet:
|
|
119
|
+
// bucketing those into the newest tag would credit people for work that has
|
|
120
|
+
// not shipped.
|
|
121
|
+
'--tags',
|
|
122
|
+
// Keeps a merged branch's commits together instead of letting their commit
|
|
123
|
+
// dates interleave them with another line of history. It is what makes the
|
|
124
|
+
// walk below equal to one `rev-list <previous>..<tag>` per release without
|
|
125
|
+
// paying for one `git` process per release.
|
|
126
|
+
'--topo-order',
|
|
127
|
+
'--decorate=short',
|
|
128
|
+
'--decorate-refs=refs/tags/*',
|
|
129
|
+
`--format=${FORMAT}`
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Every release tag, with the people whose commits it carries.
|
|
134
|
+
*
|
|
135
|
+
* ONE `git log` FOR THE WHOLE HISTORY, walked newest-first: a commit carrying a
|
|
136
|
+
* tag OPENS that tag's release, and every commit after it in the walk belongs
|
|
137
|
+
* there until the next tag opens the release before it. That is exactly the
|
|
138
|
+
* range `<previous tag>..<tag>` names, and the alternative — a `git log` per
|
|
139
|
+
* release — is the cost `modules/git-meta.ts` already paid once: the work is
|
|
140
|
+
* never the history, it is spawning the processes.
|
|
141
|
+
*
|
|
142
|
+
* A MERGE COMMIT SETS THE BOUNDARY AND CREDITS NOBODY. It has to be walked,
|
|
143
|
+
* because release-please tags the merge commit its release pull request
|
|
144
|
+
* produced and skipping it would lose the boundary altogether; it introduces no
|
|
145
|
+
* change of its own, so its author did not write the diff the release carries.
|
|
146
|
+
* The page history arrives at the same answer by another route — `--name-status`
|
|
147
|
+
* prints nothing for a merge — which is why the two surfaces agree about it.
|
|
148
|
+
*/
|
|
149
|
+
export function parseReleaseContributors(
|
|
150
|
+
output: string
|
|
151
|
+
): Map<string, DuxtContributor[]> {
|
|
152
|
+
const commits = new Map<string, DuxtCommitAuthor[]>();
|
|
153
|
+
const contributors = new Map<string, DuxtContributor[]>();
|
|
154
|
+
|
|
155
|
+
/** The tags whose release the commits now being read belong to. */
|
|
156
|
+
let current: string[] = [];
|
|
157
|
+
|
|
158
|
+
for (const record of output.split(RECORD)) {
|
|
159
|
+
if (!record.trim()) continue;
|
|
160
|
+
|
|
161
|
+
const [parents, name, email, decoration] = record
|
|
162
|
+
.split('\n')[0]!
|
|
163
|
+
.split(SEPARATOR);
|
|
164
|
+
|
|
165
|
+
const tags = tagsOf(decoration ?? '');
|
|
166
|
+
|
|
167
|
+
if (tags.length) {
|
|
168
|
+
current = tags;
|
|
169
|
+
for (const tag of tags) commits.set(tag, commits.get(tag) ?? []);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!current.length) continue;
|
|
173
|
+
|
|
174
|
+
// A merge carries more than one parent. It opened the release above and
|
|
175
|
+
// says nothing else.
|
|
176
|
+
if ((parents ?? '').trim().includes(' ')) continue;
|
|
177
|
+
|
|
178
|
+
const author = { name: name ?? '', email: email ?? '' };
|
|
179
|
+
if (isGitHubApp(author)) continue;
|
|
180
|
+
|
|
181
|
+
for (const tag of current) commits.get(tag)!.push(author);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const [tag, authors] of commits) {
|
|
185
|
+
contributors.set(tag, contributorsOf(authors));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return contributors;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The tag names in one commit's decoration.
|
|
193
|
+
*
|
|
194
|
+
* Every tag on a commit names the same range of history, so all of them are
|
|
195
|
+
* kept: a project that tags `v1.0.0` and `1.0.0` at once is answered under
|
|
196
|
+
* whichever spelling its changelog happens to use.
|
|
197
|
+
*/
|
|
198
|
+
function tagsOf(decoration: string): string[] {
|
|
199
|
+
return decoration
|
|
200
|
+
.split(',')
|
|
201
|
+
.map((entry) => entry.trim())
|
|
202
|
+
.filter((entry) => entry.startsWith('tag: '))
|
|
203
|
+
.map((entry) => entry.slice('tag: '.length).trim())
|
|
204
|
+
.filter(Boolean);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Two ways of writing a repository, reduced to the one thing that identifies it.
|
|
209
|
+
*
|
|
210
|
+
* `kirchDev/duxt` resolves to `https://github.com/kirchDev/duxt`, a checkout's
|
|
211
|
+
* remote is routinely `git@github.com:kirchDev/duxt.git`, and both name the same
|
|
212
|
+
* repository. Scheme, credentials, the scp-style colon, a trailing `.git` and
|
|
213
|
+
* case go; GitHub treats owner and name case-insensitively, and so does this.
|
|
214
|
+
*/
|
|
215
|
+
export function sameRepository(a: string, b: string): boolean {
|
|
216
|
+
const key = (url: string) =>
|
|
217
|
+
url
|
|
218
|
+
.trim()
|
|
219
|
+
.replace(/^[a-z][a-z+.-]*:\/\//i, '')
|
|
220
|
+
.replace(/^[^@/]+@/, '')
|
|
221
|
+
.replace(/^([^/:]+):(?!\d+\/)/, '$1/')
|
|
222
|
+
.replace(/\.git\/?$/i, '')
|
|
223
|
+
.replace(/\/+$/, '')
|
|
224
|
+
.toLowerCase();
|
|
225
|
+
|
|
226
|
+
return key(a) === key(b);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The local checkout's history, where a DOWNLOADED source is this very
|
|
231
|
+
* repository.
|
|
232
|
+
*
|
|
233
|
+
* Content clones a remote source at `--depth 1`, and a history like that is
|
|
234
|
+
* wrong data rather than missing data — which is why a downloaded changelog
|
|
235
|
+
* reads no contributors. But a site very often names ITS OWN repository as a
|
|
236
|
+
* source, to publish the documentation of a tag rather than of the commit being
|
|
237
|
+
* built, and then the full history is already on disk: the tags are the same
|
|
238
|
+
* tags, so a release is the same range of commits whichever copy is asked.
|
|
239
|
+
*
|
|
240
|
+
* Every remote counts, not only `origin`: a fork's checkout calls the project
|
|
241
|
+
* `upstream`. A failure is silent for the reason `releaseContributors` gives —
|
|
242
|
+
* nothing was configured, so no git means what it meant before.
|
|
243
|
+
*/
|
|
244
|
+
export function localHistoryFor(url: string, root: string): string | undefined {
|
|
245
|
+
try {
|
|
246
|
+
const remotes = execFileSync('git', ['-C', root, 'remote', '-v'], {
|
|
247
|
+
encoding: 'utf8',
|
|
248
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return remotes
|
|
252
|
+
.split('\n')
|
|
253
|
+
.map((line) => line.split(/\s+/)[1])
|
|
254
|
+
.some((remote) => remote && sameRepository(remote, url))
|
|
255
|
+
? root
|
|
256
|
+
: undefined;
|
|
257
|
+
} catch {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** One answer per repository, read the first time a section asks for it. */
|
|
263
|
+
const cache = new Map<string, Map<string, DuxtContributor[]>>();
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The releases of the checkout at `root`, by tag.
|
|
267
|
+
*
|
|
268
|
+
* A FAILURE IS SILENT, and deliberately not the reporting `sources-git.ts`
|
|
269
|
+
* does: there, tags that cannot be read are a source a maintainer has to fix,
|
|
270
|
+
* and "found no tags" sent them to edit a source that was never wrong. Here
|
|
271
|
+
* nothing was configured at all — the history is read because it happens to be
|
|
272
|
+
* there — so no git, no checkout and a repository that has never been tagged
|
|
273
|
+
* all mean what they mean in `modules/git-meta.ts`: the page shows what it
|
|
274
|
+
* showed before this module existed.
|
|
275
|
+
*/
|
|
276
|
+
export function releaseContributors(
|
|
277
|
+
root: string
|
|
278
|
+
): Map<string, DuxtContributor[]> {
|
|
279
|
+
const cached = cache.get(root);
|
|
280
|
+
if (cached) return cached;
|
|
281
|
+
|
|
282
|
+
let found: Map<string, DuxtContributor[]>;
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
found = parseReleaseContributors(
|
|
286
|
+
execFileSync('git', ['-C', root, ...ARGUMENTS], {
|
|
287
|
+
encoding: 'utf8',
|
|
288
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
289
|
+
// A repository's whole log against execFileSync's 1 MB default is a
|
|
290
|
+
// truncated history reported as a git failure.
|
|
291
|
+
maxBuffer: Number.POSITIVE_INFINITY
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
} catch {
|
|
295
|
+
found = new Map();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
cache.set(root, found);
|
|
299
|
+
return found;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The people a changelog's version names, matched to the tag it was cut as.
|
|
304
|
+
*
|
|
305
|
+
* A changelog heading says `0.2.0` and the tag says `v0.2.0` — release-please's
|
|
306
|
+
* `include-v-in-tag` — so both spellings are tried, in both directions. Nothing
|
|
307
|
+
* is inferred beyond that: a version with no tag is a release this checkout
|
|
308
|
+
* cannot see (one cut after the build, or a hand-written fixture), and the
|
|
309
|
+
* honest answer for it is no contributors rather than someone else's.
|
|
310
|
+
*/
|
|
311
|
+
export function contributorsForVersion(
|
|
312
|
+
releases: Map<string, DuxtContributor[]> | undefined,
|
|
313
|
+
version: string
|
|
314
|
+
): DuxtContributor[] | undefined {
|
|
315
|
+
if (!releases) return undefined;
|
|
316
|
+
|
|
317
|
+
const bare = version.replace(/^v/, '');
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
releases.get(version) ?? releases.get(`v${bare}`) ?? releases.get(bare)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"duxt": {
|
|
3
|
+
"bruno": {
|
|
4
|
+
"auth": "Authentifizierung",
|
|
5
|
+
"body": "Body",
|
|
6
|
+
"download": "Collection herunterladen",
|
|
7
|
+
"downloadHint": "Die Collection genau so, wie diese Version und Sprache sie beschreiben.",
|
|
8
|
+
"environments": "Umgebungen",
|
|
9
|
+
"fetch": "In Bruno öffnen",
|
|
10
|
+
"fetchHint": "Klont das Repository in Bruno. Es folgt dem Standard-Branch und ist nicht an diese Version gebunden.",
|
|
11
|
+
"folders": "Ordner",
|
|
12
|
+
"format": "Format",
|
|
13
|
+
"getIt": "Collection beziehen",
|
|
14
|
+
"hasCode": "Diese Anfrage enthält außerdem Skripte oder Tests. Sie werden hier weder angezeigt noch ausgeführt; im Download sind sie enthalten.",
|
|
15
|
+
"headers": "Header",
|
|
16
|
+
"name": "Name",
|
|
17
|
+
"notPublished": "Umgebungen, Zugangsdaten, Skripte und Tests werden auf dieser Seite nie veröffentlicht und nie im Browser ausgeführt.",
|
|
18
|
+
"parameters": "Parameter",
|
|
19
|
+
"requests": "Anfragen",
|
|
20
|
+
"shared": "Für alle Anfragen gemeinsam",
|
|
21
|
+
"value": "Wert",
|
|
22
|
+
"withheld": "zurückgehalten"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
"preview": "Vorschau",
|
|
8
8
|
"source": "Code",
|
|
9
9
|
"previewTabs": "Beispiel und Quelltext",
|
|
10
|
-
"copiedToast": "In die Zwischenablage kopiert"
|
|
10
|
+
"copiedToast": "In die Zwischenablage kopiert",
|
|
11
|
+
"copyFailed": "Konnte nicht in die Zwischenablage kopieren."
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
"no": "Nein",
|
|
14
14
|
"thanks": "Vielen Dank."
|
|
15
15
|
},
|
|
16
|
+
"image": {
|
|
17
|
+
"close": "Bildansicht schließen",
|
|
18
|
+
"original": "Original in neuem Tab öffnen",
|
|
19
|
+
"resetZoom": "Zoom zurücksetzen",
|
|
20
|
+
"zoom": "Bild vergrößern",
|
|
21
|
+
"zoomIn": "Vergrößern",
|
|
22
|
+
"zoomNamed": "Bild vergrößern: {alt}",
|
|
23
|
+
"zoomOut": "Verkleinern"
|
|
24
|
+
},
|
|
16
25
|
"tabs": "Varianten",
|
|
17
26
|
"since": "Neu in {version}",
|
|
18
27
|
"copy": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"duxt": {
|
|
3
|
+
"bruno": {
|
|
4
|
+
"auth": "Authentication",
|
|
5
|
+
"body": "Body",
|
|
6
|
+
"download": "Download collection",
|
|
7
|
+
"downloadHint": "The collection exactly as this version and language describe it.",
|
|
8
|
+
"environments": "Environments",
|
|
9
|
+
"fetch": "Fetch in Bruno",
|
|
10
|
+
"fetchHint": "Clones the repository in Bruno. It follows the default branch and is not pinned to this version.",
|
|
11
|
+
"folders": "Folders",
|
|
12
|
+
"format": "Format",
|
|
13
|
+
"getIt": "Get the collection",
|
|
14
|
+
"hasCode": "This request also carries scripts or tests. They are neither shown here nor run; the download has them.",
|
|
15
|
+
"headers": "Headers",
|
|
16
|
+
"name": "Name",
|
|
17
|
+
"notPublished": "Environments, credentials, scripts and tests are never published on this site and never run in your browser.",
|
|
18
|
+
"parameters": "Parameters",
|
|
19
|
+
"requests": "Requests",
|
|
20
|
+
"shared": "Shared by every request",
|
|
21
|
+
"value": "Value",
|
|
22
|
+
"withheld": "withheld"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
"no": "No",
|
|
14
14
|
"thanks": "Thank you."
|
|
15
15
|
},
|
|
16
|
+
"image": {
|
|
17
|
+
"close": "Close image viewer",
|
|
18
|
+
"original": "Open original in a new tab",
|
|
19
|
+
"resetZoom": "Reset zoom",
|
|
20
|
+
"zoom": "Enlarge image",
|
|
21
|
+
"zoomIn": "Zoom in",
|
|
22
|
+
"zoomNamed": "Enlarge image: {alt}",
|
|
23
|
+
"zoomOut": "Zoom out"
|
|
24
|
+
},
|
|
16
25
|
"tabs": "Options",
|
|
17
26
|
"since": "New in {version}",
|
|
18
27
|
"copy": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"duxt": {
|
|
3
|
+
"bruno": {
|
|
4
|
+
"auth": "Autenticación",
|
|
5
|
+
"body": "Cuerpo",
|
|
6
|
+
"download": "Descargar la colección",
|
|
7
|
+
"downloadHint": "La colección tal y como la describen esta versión y este idioma.",
|
|
8
|
+
"environments": "Entornos",
|
|
9
|
+
"fetch": "Abrir en Bruno",
|
|
10
|
+
"fetchHint": "Clona el repositorio en Bruno. Sigue la rama por defecto y no está fijado a esta versión.",
|
|
11
|
+
"folders": "Carpetas",
|
|
12
|
+
"format": "Formato",
|
|
13
|
+
"getIt": "Obtener la colección",
|
|
14
|
+
"hasCode": "Esta petición también incluye scripts o pruebas. Aquí no se muestran ni se ejecutan; están en la descarga.",
|
|
15
|
+
"headers": "Cabeceras",
|
|
16
|
+
"name": "Nombre",
|
|
17
|
+
"notPublished": "Los entornos, las credenciales, los scripts y las pruebas nunca se publican en este sitio ni se ejecutan en tu navegador.",
|
|
18
|
+
"parameters": "Parámetros",
|
|
19
|
+
"requests": "Peticiones",
|
|
20
|
+
"shared": "Común a todas las peticiones",
|
|
21
|
+
"value": "Valor",
|
|
22
|
+
"withheld": "oculto"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
"no": "No",
|
|
14
14
|
"thanks": "Gracias."
|
|
15
15
|
},
|
|
16
|
+
"image": {
|
|
17
|
+
"close": "Cerrar el visor de imágenes",
|
|
18
|
+
"original": "Abrir el original en una pestaña nueva",
|
|
19
|
+
"resetZoom": "Restablecer el zoom",
|
|
20
|
+
"zoom": "Ampliar imagen",
|
|
21
|
+
"zoomIn": "Acercar",
|
|
22
|
+
"zoomNamed": "Ampliar imagen: {alt}",
|
|
23
|
+
"zoomOut": "Alejar"
|
|
24
|
+
},
|
|
16
25
|
"tabs": "Opciones",
|
|
17
26
|
"since": "Novedad en {version}",
|
|
18
27
|
"copy": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"duxt": {
|
|
3
|
+
"bruno": {
|
|
4
|
+
"auth": "Authentification",
|
|
5
|
+
"body": "Corps",
|
|
6
|
+
"download": "Télécharger la collection",
|
|
7
|
+
"downloadHint": "La collection telle que cette version et cette langue la décrivent.",
|
|
8
|
+
"environments": "Environnements",
|
|
9
|
+
"fetch": "Ouvrir dans Bruno",
|
|
10
|
+
"fetchHint": "Clone le dépôt dans Bruno. Il suit la branche par défaut et n'est pas figé sur cette version.",
|
|
11
|
+
"folders": "Dossiers",
|
|
12
|
+
"format": "Format",
|
|
13
|
+
"getIt": "Obtenir la collection",
|
|
14
|
+
"hasCode": "Cette requête comporte aussi des scripts ou des tests. Ils ne sont ni affichés ni exécutés ici ; le téléchargement les contient.",
|
|
15
|
+
"headers": "En-têtes",
|
|
16
|
+
"name": "Nom",
|
|
17
|
+
"notPublished": "Les environnements, les identifiants, les scripts et les tests ne sont jamais publiés sur ce site ni exécutés dans votre navigateur.",
|
|
18
|
+
"parameters": "Paramètres",
|
|
19
|
+
"requests": "Requêtes",
|
|
20
|
+
"shared": "Commun à toutes les requêtes",
|
|
21
|
+
"value": "Valeur",
|
|
22
|
+
"withheld": "masqué"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"filter": "Filtrer par type de modification",
|
|
6
6
|
"latest": "Plus récente",
|
|
7
7
|
"changes": "Modifications",
|
|
8
|
-
"
|
|
8
|
+
"contributors": "Contributeurs",
|
|
9
9
|
"last": "Dernière version",
|
|
10
10
|
"more": "et {count} de plus",
|
|
11
11
|
"kind": "Type de modification",
|