@inditextech/docouture-cli 1.0.0 → 1.1.1
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/build/commands/upgrade.js +23 -8
- package/build/lib/agents-md.js +9 -1
- package/build/lib/dev-server.js +1 -1
- package/build/templates/starter/scripts/check-links.mjs +24 -2
- package/build/templates/workflows/docouture-kroki-cache-warm.yml +8 -0
- package/build/templates/workflows/docouture-pr-verify.yml +12 -1
- package/build/templates/workflows/docouture-release-preview.yml +14 -0
- package/build/templates/workflows/docouture-release.yml +58 -8
- package/package.json +1 -1
|
@@ -68,6 +68,14 @@ export async function runUpgrade(argv) {
|
|
|
68
68
|
const templatesRoot = join(here, '..', 'templates');
|
|
69
69
|
const workflowsTemplateDir = join(templatesRoot, 'workflows');
|
|
70
70
|
const agentSupportDir = join(templatesRoot, 'agent-support');
|
|
71
|
+
// Unlike the rest of `docs/` (never re-copied — see the comment above the
|
|
72
|
+
// dry-run/real-run split below), `scripts/check-links.mjs` gets the same
|
|
73
|
+
// "regenerate wholesale" treatment as workflows: it carries zero
|
|
74
|
+
// `__DOCOUTURE_*__` placeholder tokens (nothing site-specific to lose),
|
|
75
|
+
// and its own comments already say the one thing a site owner is meant
|
|
76
|
+
// to customise — which links to ignore — belongs in package.json's
|
|
77
|
+
// `docouture.checkLinks.ignore`, not in edits to the script body itself.
|
|
78
|
+
const scriptsTemplateDir = join(templatesRoot, 'starter', 'scripts');
|
|
71
79
|
// build/commands/upgrade.js -> package root, 2 levels up — see
|
|
72
80
|
// readCliInfo's own comment. Always re-read fresh: an upgrade run re-pins
|
|
73
81
|
// whatever templates reference the CLI version to the one actually
|
|
@@ -151,30 +159,36 @@ export async function runUpgrade(argv) {
|
|
|
151
159
|
};
|
|
152
160
|
const workflowsDir = join(target, '.github', 'workflows');
|
|
153
161
|
const agentsMdFile = join(target, AGENTS_MD_FILENAME);
|
|
162
|
+
const scriptsDir = join(target, 'docs', 'scripts');
|
|
154
163
|
// Unlike `new.ts`, this command's whole purpose is to overwrite what's
|
|
155
164
|
// already there — workflows are meant to be regenerable from the
|
|
156
165
|
// template on every upgrade, not merged with local edits (there is no
|
|
157
166
|
// content-hash/diff tracking anywhere in this CLI to tell a stock file
|
|
158
167
|
// from a user-edited one). `docs/` itself — the starter content a site
|
|
159
|
-
// has since written its own pages into — is never touched here
|
|
168
|
+
// has since written its own pages into — is never touched here, with one
|
|
169
|
+
// exception: `docs/scripts/` gets the same blind-overwrite treatment as
|
|
170
|
+
// workflows (see scriptsTemplateDir's own comment above for why). Skills
|
|
160
171
|
// are never touched here either: `docouture upgrade` only re-syncs the
|
|
161
172
|
// starter site and its GitHub workflows, same as `docouture new` only
|
|
162
173
|
// scaffolds them — skills are a separate, self-serve install via
|
|
163
|
-
// `npx skills add InditexTech/docouture`. AGENTS.md is the
|
|
164
|
-
// copyTemplate's own SKIP_FILENAMES skip (see
|
|
165
|
-
// untouched by the walk above, and it's
|
|
166
|
-
// lib/agents-md.ts for why a blind overwrite here
|
|
167
|
-
// the 'Documentation state' table the
|
|
168
|
-
// maintains outside docouture' own
|
|
174
|
+
// `npx skills add InditexTech/docouture`. AGENTS.md is the other
|
|
175
|
+
// exception: copyTemplate's own SKIP_FILENAMES skip (see
|
|
176
|
+
// copy-template.ts) leaves it untouched by the walk above, and it's
|
|
177
|
+
// merged instead — see lib/agents-md.ts for why a blind overwrite here
|
|
178
|
+
// would silently destroy the 'Documentation state' table the
|
|
179
|
+
// docouture-documenting-changes skill maintains outside docouture' own
|
|
180
|
+
// managed section.
|
|
169
181
|
if (dryRun) {
|
|
170
182
|
const plannedWorkflows = await copyTemplate(workflowsTemplateDir, workflowsDir, values, { dryRun: true });
|
|
183
|
+
const plannedScripts = await copyTemplate(scriptsTemplateDir, scriptsDir, values, { dryRun: true });
|
|
171
184
|
console.log('would write:');
|
|
172
|
-
for (const path of [...plannedWorkflows, agentsMdFile]) {
|
|
185
|
+
for (const path of [...plannedWorkflows, ...plannedScripts, agentsMdFile]) {
|
|
173
186
|
console.log(` ${relative(target, path)}`);
|
|
174
187
|
}
|
|
175
188
|
return 0;
|
|
176
189
|
}
|
|
177
190
|
await copyTemplate(workflowsTemplateDir, workflowsDir, values);
|
|
191
|
+
await copyTemplate(scriptsTemplateDir, scriptsDir, values);
|
|
178
192
|
const existingAgentsMd = (await exists(agentsMdFile)) ? await readFile(agentsMdFile, 'utf8') : undefined;
|
|
179
193
|
const renderedAgentsMd = await renderTemplateFile(join(agentSupportDir, AGENTS_MD_FILENAME), values);
|
|
180
194
|
await writeFile(agentsMdFile, mergeAgentsMd(existingAgentsMd, renderedAgentsMd), 'utf8');
|
|
@@ -183,6 +197,7 @@ export async function runUpgrade(argv) {
|
|
|
183
197
|
// relative(cwd, ...) produces a useless '../../..' chain for paths that
|
|
184
198
|
// are actually just '.github/workflows', 'AGENTS.md' etc. at the root.
|
|
185
199
|
console.log(`updated ${relative(target, workflowsDir)}`);
|
|
200
|
+
console.log(`updated ${relative(target, scriptsDir)}`);
|
|
186
201
|
console.log(`updated ${relative(target, agentsMdFile)}`);
|
|
187
202
|
return 0;
|
|
188
203
|
}
|
package/build/lib/agents-md.js
CHANGED
|
@@ -64,7 +64,15 @@ export function mergeAgentsMd(existing, templateContent) {
|
|
|
64
64
|
const after = existing.slice(end + MANAGED_END.length);
|
|
65
65
|
return `${before}${extractManagedBlock(templateContent)}${after}`;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
let separator;
|
|
68
|
+
if (existing.length === 0)
|
|
69
|
+
separator = '';
|
|
70
|
+
else if (existing.endsWith('\n\n'))
|
|
71
|
+
separator = '';
|
|
72
|
+
else if (existing.endsWith('\n'))
|
|
73
|
+
separator = '\n';
|
|
74
|
+
else
|
|
75
|
+
separator = '\n\n';
|
|
68
76
|
return `${existing}${separator}${templateContent}`;
|
|
69
77
|
}
|
|
70
78
|
//# sourceMappingURL=agents-md.js.map
|
package/build/lib/dev-server.js
CHANGED
|
@@ -365,7 +365,7 @@ export async function startDevServer(options) {
|
|
|
365
365
|
// otherwise be left running as an orphan after the dev server itself
|
|
366
366
|
// has already torn down — kill it too, best-effort (it may already
|
|
367
367
|
// have exited on its own by the time close() runs).
|
|
368
|
-
if (activeBuildChild
|
|
368
|
+
if (activeBuildChild?.exitCode === null && !activeBuildChild?.killed) {
|
|
369
369
|
activeBuildChild.kill('SIGTERM');
|
|
370
370
|
}
|
|
371
371
|
for (const res of clients)
|
|
@@ -63,8 +63,8 @@ const isExternal = (url) => /^https?:\/\//.test(url)
|
|
|
63
63
|
// every other regex metacharacter so the rest of the string is matched
|
|
64
64
|
// verbatim.
|
|
65
65
|
function globToRegExp(glob) {
|
|
66
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g,
|
|
67
|
-
return new RegExp(escaped.
|
|
66
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, String.raw`\$&`)
|
|
67
|
+
return new RegExp(escaped.replaceAll('*', '.*').replaceAll('?', '.'))
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// `package.json`'s own "docouture" config block (see the scaffolded stub, next
|
|
@@ -140,6 +140,28 @@ const result = await check({
|
|
|
140
140
|
path: 'build/site',
|
|
141
141
|
recurse: true,
|
|
142
142
|
linksToSkip: SKIP,
|
|
143
|
+
// linkinator's own default is 0 — NO application-level timeout at all
|
|
144
|
+
// (see its own README: "requests made by linkinator do not time out, or
|
|
145
|
+
// follow the settings of the OS"), which means `request.js`'s
|
|
146
|
+
// `AbortSignal.timeout(options.timeout)` is never even constructed. A
|
|
147
|
+
// real multi-page site can easily produce a few hundred *distinct*
|
|
148
|
+
// (not duplicate — linkinator already dedupes identical URLs, so this
|
|
149
|
+
// isn't a dedup gap) links to the same external host: think a
|
|
150
|
+
// CHANGELOG page with one github.com/…/pull/NNN link per entry, not
|
|
151
|
+
// just this template's own repo-link/edit-link. A burst that size can
|
|
152
|
+
// trip a host's own anonymous-crawler rate limiting (GitHub's included),
|
|
153
|
+
// and — unconfirmed upstream, but plausible and cheap to guard against
|
|
154
|
+
// either way — if that limiting responds slowly rather than rejecting
|
|
155
|
+
// fast, an unbounded request ties up one of `concurrency`'s 100 slots
|
|
156
|
+
// for however long the OS's own idle/keepalive timeout happens to be
|
|
157
|
+
// (which can be minutes), not a few seconds. 10s is generous for any
|
|
158
|
+
// real external page load; it only ever kicks in to cut a stalled
|
|
159
|
+
// socket loose instead of quietly inflating the whole crawl's wall-clock
|
|
160
|
+
// time. NOT a fix for "too many links to github.com" in general — those
|
|
161
|
+
// are real, individually-distinct links an author is vouching for, and
|
|
162
|
+
// correctly stay off `docouture.checkLinks.ignore` (see ignorePatterns()
|
|
163
|
+
// above) so a genuinely broken one still fails the build.
|
|
164
|
+
timeout: 10_000,
|
|
143
165
|
// A plain 403/429 from a real external host (most commonly GitHub's own
|
|
144
166
|
// bot/rate-limit protection kicking in on repo links, hit repeatedly
|
|
145
167
|
// across every page of a freshly built site) can't be told apart from a
|
|
@@ -37,9 +37,17 @@ name: docouture-kroki-cache-warm
|
|
|
37
37
|
#
|
|
38
38
|
# Gated on `kroki-enabled` the same way those three are — no point warming a
|
|
39
39
|
# cache for images a disabled site's build will never touch.
|
|
40
|
+
#
|
|
41
|
+
# Path-filtered for the same reason docouture-pr-verify.yml is: a push that
|
|
42
|
+
# touches neither `docs/` nor this file has nothing for this job to usefully
|
|
43
|
+
# warm a cache for. Nothing else in this repo depends on this job running,
|
|
44
|
+
# so skipping it entirely (rather than a no-op run) is safe by construction.
|
|
40
45
|
on:
|
|
41
46
|
push:
|
|
42
47
|
branches: [__DOCOUTURE_CACHE_WARM_BRANCHES__]
|
|
48
|
+
paths:
|
|
49
|
+
- 'docs/**'
|
|
50
|
+
- '.github/workflows/docouture-kroki-cache-warm.yml'
|
|
43
51
|
|
|
44
52
|
# Least-privilege default: this job only checks out the release/prerelease
|
|
45
53
|
# branch it happened to run against, warms the Docker/Kroki image cache and
|
|
@@ -16,8 +16,19 @@ name: docouture-pr-verify
|
|
|
16
16
|
# that file's own header) exists for exactly this: one content source,
|
|
17
17
|
# `branches: HEAD`, so this always validates whatever the PR actually
|
|
18
18
|
# changed — no `fetch-depth: 0` needed, since no other ref is ever read.
|
|
19
|
+
#
|
|
20
|
+
# Path-filtered so it doesn't run at all on a PR that touches neither `docs/`
|
|
21
|
+
# content nor this file — nothing else in this job's build can be affected by
|
|
22
|
+
# changes elsewhere in the repo. If this job (`Verify`, this workflow) is a
|
|
23
|
+
# required status check in your own branch protection, confirm that's set up
|
|
24
|
+
# to tolerate a skipped run (e.g. GitHub's "Do not require status checks on
|
|
25
|
+
# creation" / rulesets' own equivalent) rather than blocking merges on a
|
|
26
|
+
# check that now never reports for docs-untouched PRs.
|
|
19
27
|
on:
|
|
20
|
-
pull_request:
|
|
28
|
+
pull_request:
|
|
29
|
+
paths:
|
|
30
|
+
- 'docs/**'
|
|
31
|
+
- '.github/workflows/docouture-pr-verify.yml'
|
|
21
32
|
|
|
22
33
|
# Least-privilege default: this job only checks out the PR's own HEAD,
|
|
23
34
|
# builds it, and checks links — it never writes to the repository, comments
|
|
@@ -152,6 +152,20 @@ jobs:
|
|
|
152
152
|
exit 0
|
|
153
153
|
fi
|
|
154
154
|
|
|
155
|
+
# Same full SemVer 2.0.0 grammar docouture-release.yml's own
|
|
156
|
+
# "Validate version" step checks — see that step's own comment for
|
|
157
|
+
# why plain groups instead of `(?:...)` (bash's [[ =~ ]] runs glibc
|
|
158
|
+
# POSIX ERE, not PCRE). Catching this here, before merge, is the
|
|
159
|
+
# whole point of a preview: docouture-release.yml checks the same
|
|
160
|
+
# thing, but only finds out after the PR has already merged.
|
|
161
|
+
SEMVER_RE='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$'
|
|
162
|
+
if [ "$MODE" = "versioned" ] && ! [[ "$VERSION" =~ $SEMVER_RE ]]; then
|
|
163
|
+
gh pr comment ${{ github.event.number }} --body "
|
|
164
|
+
### :x: docs/release: invalid target version
|
|
165
|
+
\`docs/.release-version\` contains \`${VERSION}\`, which is not a valid SemVer version — use something like \`1.2.0\` (see https://semver.org). A leading \`v\` or a two-part \`1.2\` are not valid; the release workflow will fail on merge otherwise."
|
|
166
|
+
exit 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
155
169
|
MESSAGE="
|
|
156
170
|
### :rocket: Docs Release Preview
|
|
157
171
|
Merging this Pull Request will release the docs site as **\`${TAG}\`**."
|
|
@@ -20,7 +20,7 @@ name: docouture-release
|
|
|
20
20
|
# branch roles mean (trunk-based: both the same branch; git-flow: two
|
|
21
21
|
# independently-named branches, e.g. `develop`/`main`).
|
|
22
22
|
#
|
|
23
|
-
#
|
|
23
|
+
# Three ways to run this, all deliberate acts rather than a side effect of an
|
|
24
24
|
# ordinary push:
|
|
25
25
|
#
|
|
26
26
|
# 1. workflow_dispatch — a person runs it by hand and supplies `version`
|
|
@@ -32,6 +32,28 @@ name: docouture-release
|
|
|
32
32
|
# versioned reads it from `docs/.release-version`, a plain-text file
|
|
33
33
|
# the PR itself commits with the target version — reviewed as part of
|
|
34
34
|
# that PR's diff, like any other change.
|
|
35
|
+
# 3. Automatically, whenever a GitHub Release is published on this repo.
|
|
36
|
+
# Irrelevant unless this repo ALSO cuts its own code releases some other
|
|
37
|
+
# way, labelling the merged PR `release-type/*` (optionally
|
|
38
|
+
# `skip-release`) the way docouture's own repo does — nothing scaffolds
|
|
39
|
+
# that convention here, so on a fresh `docouture new` site this trigger
|
|
40
|
+
# simply never fires and the job below always takes path #2 instead.
|
|
41
|
+
# Kept anyway, disabled by omission rather than left out, so adopting
|
|
42
|
+
# that convention later is a label/workflow change only, not a rewrite
|
|
43
|
+
# of this file too.
|
|
44
|
+
#
|
|
45
|
+
# Where it does apply: this overlaps with #2 — a single merged PR can
|
|
46
|
+
# carry BOTH `docs/release` and a `release-type/*` label (releasing
|
|
47
|
+
# code and docs together). If it did, and #2 fired unconditionally,
|
|
48
|
+
# this workflow would run twice for the one merge — once immediately
|
|
49
|
+
# (the `pull_request` close) and again once that other release job
|
|
50
|
+
# finishes and publishes its Release (the trigger added here). The
|
|
51
|
+
# job's own `if:` below avoids that: the `pull_request` branch only
|
|
52
|
+
# fires when a code release is NOT also about to happen for the same
|
|
53
|
+
# merge (no `release-type/*` label, or `skip-release` overrides it) —
|
|
54
|
+
# otherwise it backs off and leaves the job to trigger #3 instead, once
|
|
55
|
+
# that other workflow actually finishes. Either way this runs exactly
|
|
56
|
+
# once per merge, just not always at the same moment.
|
|
35
57
|
#
|
|
36
58
|
# Republishing (versioned only): a fix that needs to land on an
|
|
37
59
|
# already-released version overwrites that version's tag — this workflow
|
|
@@ -57,6 +79,8 @@ on:
|
|
|
57
79
|
pull_request:
|
|
58
80
|
types: [closed]
|
|
59
81
|
branches: ['__DOCOUTURE_RELEASE_BRANCH__*']
|
|
82
|
+
release:
|
|
83
|
+
types: [published]
|
|
60
84
|
|
|
61
85
|
permissions:
|
|
62
86
|
contents: write
|
|
@@ -81,16 +105,23 @@ jobs:
|
|
|
81
105
|
defaults:
|
|
82
106
|
run:
|
|
83
107
|
working-directory: docs
|
|
84
|
-
# workflow_dispatch
|
|
85
|
-
#
|
|
86
|
-
# the release branch — including ones that were
|
|
87
|
-
# merged without the label — so it only proceeds
|
|
88
|
-
#
|
|
89
|
-
#
|
|
108
|
+
# workflow_dispatch and a published Release both always proceed (already
|
|
109
|
+
# explicit, deliberate acts). The pull_request trigger fires on every
|
|
110
|
+
# close of a PR targeting the release branch — including ones that were
|
|
111
|
+
# closed unmerged, or merged without the label — so it only proceeds
|
|
112
|
+
# when merged, carrying `docs/release`, AND a same-merge code release is
|
|
113
|
+
# NOT also about to fire under the `release-type`/`skip-release`
|
|
114
|
+
# convention (see this file's own header comment on trigger #2/#3 — a
|
|
115
|
+
# no-op on a site that doesn't use that convention, since
|
|
116
|
+
# `release-type`/`skip-release` labels then never exist to contain).
|
|
117
|
+
# Anything else is a no-op run, not an error.
|
|
90
118
|
if: >-
|
|
91
119
|
github.event_name == 'workflow_dispatch' ||
|
|
120
|
+
github.event_name == 'release' ||
|
|
92
121
|
(github.event.pull_request.merged == true &&
|
|
93
|
-
contains(github.event.pull_request.labels.*.name, 'docs/release')
|
|
122
|
+
contains(github.event.pull_request.labels.*.name, 'docs/release') &&
|
|
123
|
+
(contains(github.event.pull_request.labels.*.name, 'skip-release') ||
|
|
124
|
+
!contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type')))
|
|
94
125
|
steps:
|
|
95
126
|
- name: Require release branch
|
|
96
127
|
# Only meaningful for a manual dispatch: someone could run this
|
|
@@ -302,6 +333,25 @@ jobs:
|
|
|
302
333
|
exit 1
|
|
303
334
|
fi
|
|
304
335
|
|
|
336
|
+
# Full SemVer 2.0.0 grammar (semver.org's own regex, rewritten with
|
|
337
|
+
# plain groups instead of `(?:...)` — bash's [[ =~ ]] runs glibc
|
|
338
|
+
# POSIX ERE, not PCRE, and doesn't understand non-capturing
|
|
339
|
+
# groups). Catches a hand-edited docs/.release-version (or a typo'd
|
|
340
|
+
# workflow_dispatch input) BEFORE any tag is pushed below — without
|
|
341
|
+
# this, a malformed value sails through here, tags and pushes fine
|
|
342
|
+
# in "Cut release"/"Push release tag", and only fails later inside
|
|
343
|
+
# "Bump release descriptor" (npm's `version patch` rejects
|
|
344
|
+
# non-semver input) — by which point the tag already exists but the
|
|
345
|
+
# job as a whole reads as failed, so the `publish` job below never
|
|
346
|
+
# runs. Prerelease/build-metadata suffixes (1.2.0-beta.1,
|
|
347
|
+
# 1.2.0+build) are valid SemVer and accepted; a leading 'v' or a
|
|
348
|
+
# bare '1.2' are not.
|
|
349
|
+
SEMVER_RE='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$'
|
|
350
|
+
if [ "$MODE" = "versioned" ] && [ -n "$VERSION" ] && ! [[ "$VERSION" =~ $SEMVER_RE ]]; then
|
|
351
|
+
echo "::error::docs/.release-version (or the 'version' input) must be a valid SemVer version like 1.2.0 — got '${VERSION}'. See https://semver.org — prerelease/build metadata (1.2.0-beta.1, 1.2.0+build) is fine, a leading 'v' or a two-part '1.2' is not."
|
|
352
|
+
exit 1
|
|
353
|
+
fi
|
|
354
|
+
|
|
305
355
|
if [ "$MODE" = "standalone" ] && [ -n "$VERSION" ] && [ "$VERSION" != "stable" ]; then
|
|
306
356
|
echo "::warning::Standalone mode always releases to the 'stable' tag — ignoring provided version '${VERSION}'."
|
|
307
357
|
fi
|
package/package.json
CHANGED