@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.
@@ -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. Skills
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 one exception:
164
- // copyTemplate's own SKIP_FILENAMES skip (see copy-template.ts) leaves it
165
- // untouched by the walk above, and it's merged instead — see
166
- // lib/agents-md.ts for why a blind overwrite here would silently destroy
167
- // the 'Documentation state' table the docouture-documenting-changes skill
168
- // maintains outside docouture' own managed section.
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
  }
@@ -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
- const separator = existing.length === 0 ? '' : existing.endsWith('\n\n') ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
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
@@ -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 && activeBuildChild.exitCode === null && !activeBuildChild.killed) {
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.replace(/\*/g, '.*').replace(/\?/g, '.'))
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
- # Two ways to run this, both deliberate acts rather than a side effect of an
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 always proceeds (it's already an explicit, deliberate
85
- # act). The pull_request trigger fires on every close of a PR targeting
86
- # the release branch — including ones that were closed unmerged, or
87
- # merged without the label — so it only proceeds when both are true:
88
- # actually merged, and carrying `docs/release`. Anything else is a
89
- # no-op run, not an error.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inditextech/docouture-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Command-line tool for docouture documentation sites: scaffold a new site and set its Antora version outside the monorepo",
5
5
  "repository": {
6
6
  "type": "git",