@inditextech/docouture-cli 1.0.0 → 1.1.0

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}\`**."
@@ -302,6 +302,25 @@ jobs:
302
302
  exit 1
303
303
  fi
304
304
 
305
+ # Full SemVer 2.0.0 grammar (semver.org's own regex, rewritten with
306
+ # plain groups instead of `(?:...)` — bash's [[ =~ ]] runs glibc
307
+ # POSIX ERE, not PCRE, and doesn't understand non-capturing
308
+ # groups). Catches a hand-edited docs/.release-version (or a typo'd
309
+ # workflow_dispatch input) BEFORE any tag is pushed below — without
310
+ # this, a malformed value sails through here, tags and pushes fine
311
+ # in "Cut release"/"Push release tag", and only fails later inside
312
+ # "Bump release descriptor" (npm's `version patch` rejects
313
+ # non-semver input) — by which point the tag already exists but the
314
+ # job as a whole reads as failed, so the `publish` job below never
315
+ # runs. Prerelease/build-metadata suffixes (1.2.0-beta.1,
316
+ # 1.2.0+build) are valid SemVer and accepted; a leading 'v' or a
317
+ # bare '1.2' are not.
318
+ 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-]+)*)?$'
319
+ if [ "$MODE" = "versioned" ] && [ -n "$VERSION" ] && ! [[ "$VERSION" =~ $SEMVER_RE ]]; then
320
+ 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."
321
+ exit 1
322
+ fi
323
+
305
324
  if [ "$MODE" = "standalone" ] && [ -n "$VERSION" ] && [ "$VERSION" != "stable" ]; then
306
325
  echo "::warning::Standalone mode always releases to the 'stable' tag — ignoring provided version '${VERSION}'."
307
326
  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.0",
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",