@inditextech/docouture-cli 0.1.0-SNAPSHOT.90.1 → 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.
- package/build/bin.js +3 -2
- package/build/commands/branch-model.js +5 -0
- package/build/commands/doctor.js +5 -1
- package/build/commands/new.js +11 -0
- package/build/commands/upgrade.js +29 -8
- package/build/lib/agents-md.js +9 -1
- package/build/lib/copy-template.js +6 -0
- package/build/lib/dev-server.js +1 -1
- package/build/lib/doctor-checks.js +45 -0
- package/build/templates/starter/.tool-versions +2 -0
- package/build/templates/starter/gitignore +1 -0
- 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 +19 -1
- package/build/templates/workflows/docouture-release.yml +19 -0
- package/package.json +1 -1
package/build/bin.js
CHANGED
|
@@ -137,8 +137,9 @@ Example:
|
|
|
137
137
|
docouture doctor [--dir <path>] [--json]
|
|
138
138
|
|
|
139
139
|
Check that the environment and site configuration are healthy: Node
|
|
140
|
-
version, the
|
|
141
|
-
|
|
140
|
+
version, that the declared package manager (package.json's packageManager
|
|
141
|
+
field) is installed, the four names that must agree (component name, start
|
|
142
|
+
page, content path, package name), git history, that antora is installed,
|
|
142
143
|
and (advisory only) whether AGENTS.md and the scaffolded skills are
|
|
143
144
|
still present, whether the docs/release label exists, and whether the
|
|
144
145
|
declared branching model (docs/package.json's docouture.branching)
|
|
@@ -161,6 +161,11 @@ export async function runBranchModel(argv) {
|
|
|
161
161
|
// there is nothing meaningful to compute either from.
|
|
162
162
|
componentName: 'unused-by-branch-model',
|
|
163
163
|
cliVersion,
|
|
164
|
+
// .tool-versions (the only template file these two placeholders appear
|
|
165
|
+
// in) is never re-copied by branch-model — same reasoning as
|
|
166
|
+
// componentName above.
|
|
167
|
+
nodeVersion: 'unused-by-branch-model',
|
|
168
|
+
pnpmToolVersionsLine: 'unused-by-branch-model',
|
|
164
169
|
pmName: pm.pm,
|
|
165
170
|
pmCacheName: pm.cacheName,
|
|
166
171
|
pmLockfile: pm.lockfile,
|
package/build/commands/doctor.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getContext } from '../lib/cli-context.js';
|
|
|
11
11
|
import { theme } from '../lib/theme.js';
|
|
12
12
|
import { readSourceUrl, readStartPageComponent, readStartPath } from '../lib/playbook-yml.js';
|
|
13
13
|
import { detectBranches, inferBranching } from '../lib/branch-detect.js';
|
|
14
|
-
import { checkAgentFilesPresent, checkAntoraAvailable, checkBranchingAgrees, checkGitHasCommit, checkNamesAgree, checkNodeVersion, checkReleaseLabelExists, } from '../lib/doctor-checks.js';
|
|
14
|
+
import { checkAgentFilesPresent, checkAntoraAvailable, checkBranchingAgrees, checkGitHasCommit, checkNamesAgree, checkNodeVersion, checkPackageManagerAvailable, checkReleaseLabelExists, } from '../lib/doctor-checks.js';
|
|
15
15
|
async function readJson(file) {
|
|
16
16
|
try {
|
|
17
17
|
return JSON.parse(await readFile(file, 'utf8'));
|
|
@@ -82,6 +82,10 @@ export async function runDoctor(argv) {
|
|
|
82
82
|
status |= record(report, 'toolchain', nodeResult, 'fail');
|
|
83
83
|
if (!json)
|
|
84
84
|
printResult(nodeResult);
|
|
85
|
+
const pmResult = await checkPackageManagerAvailable(pkg?.packageManager ?? null);
|
|
86
|
+
status |= record(report, 'toolchain', pmResult, 'fail');
|
|
87
|
+
if (!json)
|
|
88
|
+
printResult(pmResult);
|
|
85
89
|
log('names');
|
|
86
90
|
const playbookFile = join(siteRoot, 'antora-playbook.yml');
|
|
87
91
|
const antoraYmlFile = join(siteRoot, 'src', 'antora.yml');
|
package/build/commands/new.js
CHANGED
|
@@ -475,11 +475,20 @@ export async function runNew(argv, io = defaultIO()) {
|
|
|
475
475
|
// it always matches whatever CLI actually generated it, snapshot/local
|
|
476
476
|
// releases included.
|
|
477
477
|
const { version: cliVersion } = await readCliInfo(import.meta.url, 2);
|
|
478
|
+
// Whatever Node is actually running this scaffold — same reasoning as
|
|
479
|
+
// cliVersion above (readCliInfo's own comment), just for the runtime
|
|
480
|
+
// rather than the CLI package. Written as .tool-versions' `nodejs` line.
|
|
481
|
+
const nodeVersion = process.version.replace(/^v/, '');
|
|
478
482
|
// The user's own choice (--pm, wizard answer, or the auto-guess computed
|
|
479
483
|
// above if neither was given) — never re-detected against `target`, so
|
|
480
484
|
// whatever was actually chosen/confirmed is what the workflows and
|
|
481
485
|
// printed next-steps agree on.
|
|
482
486
|
const pm = packageManagerPlan(pmChoice);
|
|
487
|
+
// .tool-versions' second line — present only for a pnpm-scaffolded site
|
|
488
|
+
// (see TemplateValues.pnpmToolVersionsLine's own comment); pm.packageManagerField
|
|
489
|
+
// is already the corepack-convention 'pnpm@<version>' string, so this
|
|
490
|
+
// just reshapes it into asdf/mise's own 'pnpm <version>' line syntax.
|
|
491
|
+
const pnpmToolVersionsLine = pm.pm === 'pnpm' ? `pnpm ${pm.packageManagerField.split('@')[1]}\n` : '';
|
|
483
492
|
const values = {
|
|
484
493
|
name,
|
|
485
494
|
title,
|
|
@@ -494,6 +503,8 @@ export async function runNew(argv, io = defaultIO()) {
|
|
|
494
503
|
// component-name check when this is the literal 'ROOT'.
|
|
495
504
|
componentName: urlSegment ? name : 'ROOT',
|
|
496
505
|
cliVersion,
|
|
506
|
+
nodeVersion,
|
|
507
|
+
pnpmToolVersionsLine,
|
|
497
508
|
pmName: pm.pm,
|
|
498
509
|
pmCacheName: pm.cacheName,
|
|
499
510
|
pmLockfile: pm.lockfile,
|
|
@@ -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
|
|
@@ -119,6 +127,12 @@ export async function runUpgrade(argv) {
|
|
|
119
127
|
// substituted anywhere upgrade touches.
|
|
120
128
|
componentName: 'unused-by-upgrade',
|
|
121
129
|
cliVersion,
|
|
130
|
+
// .tool-versions (the only template file these two placeholders appear
|
|
131
|
+
// in) is never re-copied by upgrade — same reasoning as componentName
|
|
132
|
+
// above: structurally required by TemplateValues, but nothing
|
|
133
|
+
// meaningful to compute either from here.
|
|
134
|
+
nodeVersion: 'unused-by-upgrade',
|
|
135
|
+
pnpmToolVersionsLine: 'unused-by-upgrade',
|
|
122
136
|
pmName: pm.pm,
|
|
123
137
|
pmCacheName: pm.cacheName,
|
|
124
138
|
pmLockfile: pm.lockfile,
|
|
@@ -145,30 +159,36 @@ export async function runUpgrade(argv) {
|
|
|
145
159
|
};
|
|
146
160
|
const workflowsDir = join(target, '.github', 'workflows');
|
|
147
161
|
const agentsMdFile = join(target, AGENTS_MD_FILENAME);
|
|
162
|
+
const scriptsDir = join(target, 'docs', 'scripts');
|
|
148
163
|
// Unlike `new.ts`, this command's whole purpose is to overwrite what's
|
|
149
164
|
// already there — workflows are meant to be regenerable from the
|
|
150
165
|
// template on every upgrade, not merged with local edits (there is no
|
|
151
166
|
// content-hash/diff tracking anywhere in this CLI to tell a stock file
|
|
152
167
|
// from a user-edited one). `docs/` itself — the starter content a site
|
|
153
|
-
// 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
|
|
154
171
|
// are never touched here either: `docouture upgrade` only re-syncs the
|
|
155
172
|
// starter site and its GitHub workflows, same as `docouture new` only
|
|
156
173
|
// scaffolds them — skills are a separate, self-serve install via
|
|
157
|
-
// `npx skills add InditexTech/docouture`. AGENTS.md is the
|
|
158
|
-
// copyTemplate's own SKIP_FILENAMES skip (see
|
|
159
|
-
// untouched by the walk above, and it's
|
|
160
|
-
// lib/agents-md.ts for why a blind overwrite here
|
|
161
|
-
// the 'Documentation state' table the
|
|
162
|
-
// 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.
|
|
163
181
|
if (dryRun) {
|
|
164
182
|
const plannedWorkflows = await copyTemplate(workflowsTemplateDir, workflowsDir, values, { dryRun: true });
|
|
183
|
+
const plannedScripts = await copyTemplate(scriptsTemplateDir, scriptsDir, values, { dryRun: true });
|
|
165
184
|
console.log('would write:');
|
|
166
|
-
for (const path of [...plannedWorkflows, agentsMdFile]) {
|
|
185
|
+
for (const path of [...plannedWorkflows, ...plannedScripts, agentsMdFile]) {
|
|
167
186
|
console.log(` ${relative(target, path)}`);
|
|
168
187
|
}
|
|
169
188
|
return 0;
|
|
170
189
|
}
|
|
171
190
|
await copyTemplate(workflowsTemplateDir, workflowsDir, values);
|
|
191
|
+
await copyTemplate(scriptsTemplateDir, scriptsDir, values);
|
|
172
192
|
const existingAgentsMd = (await exists(agentsMdFile)) ? await readFile(agentsMdFile, 'utf8') : undefined;
|
|
173
193
|
const renderedAgentsMd = await renderTemplateFile(join(agentSupportDir, AGENTS_MD_FILENAME), values);
|
|
174
194
|
await writeFile(agentsMdFile, mergeAgentsMd(existingAgentsMd, renderedAgentsMd), 'utf8');
|
|
@@ -177,6 +197,7 @@ export async function runUpgrade(argv) {
|
|
|
177
197
|
// relative(cwd, ...) produces a useless '../../..' chain for paths that
|
|
178
198
|
// are actually just '.github/workflows', 'AGENTS.md' etc. at the root.
|
|
179
199
|
console.log(`updated ${relative(target, workflowsDir)}`);
|
|
200
|
+
console.log(`updated ${relative(target, scriptsDir)}`);
|
|
180
201
|
console.log(`updated ${relative(target, agentsMdFile)}`);
|
|
181
202
|
return 0;
|
|
182
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
|
|
@@ -9,6 +9,12 @@ const PLACEHOLDERS = {
|
|
|
9
9
|
__DOCOUTURE_TITLE__: 'title',
|
|
10
10
|
__DOCOUTURE_COMPONENT_NAME__: 'componentName',
|
|
11
11
|
__DOCOUTURE_CLI_VERSION__: 'cliVersion',
|
|
12
|
+
__DOCOUTURE_NODE_VERSION__: 'nodeVersion',
|
|
13
|
+
// Whole-line token, same reasoning as pmSetupStepYaml's own comment below
|
|
14
|
+
// — the value (or '' for npm) already carries its own trailing newline,
|
|
15
|
+
// so substituting the token-plus-newline is what lets the line vanish
|
|
16
|
+
// entirely rather than leaving a blank line in the npm case.
|
|
17
|
+
'__DOCOUTURE_PNPM_TOOL_VERSIONS_LINE__\n': 'pnpmToolVersionsLine',
|
|
12
18
|
__DOCOUTURE_PM__: 'pmName',
|
|
13
19
|
__DOCOUTURE_PM_CACHE__: 'pmCacheName',
|
|
14
20
|
__DOCOUTURE_LOCKFILE__: 'pmLockfile',
|
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)
|
|
@@ -37,6 +37,51 @@ export function checkNodeVersion(engineRange, actualVersion) {
|
|
|
37
37
|
detail: `install Node ${wantMajor} or newer — this is what 'npm run build'/'docouture dev' will actually run under`,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Confirms the package manager a site actually declares (`package.json`'s
|
|
42
|
+
* own `packageManager` field, corepack convention, e.g. `pnpm@10.24.0`) is
|
|
43
|
+
* installed and reachable — `checkNodeVersion` above gets a shortcut
|
|
44
|
+
* (`docouture` *is* a Node process, so Node's presence is proof positive,
|
|
45
|
+
* only its version is an open question), but `docouture` doesn't run *as*
|
|
46
|
+
* npm or pnpm, so their presence has to be checked by actually spawning the
|
|
47
|
+
* binary. Presence only — no version-match against the declared field is
|
|
48
|
+
* required, the field just says which binary to look for.
|
|
49
|
+
*
|
|
50
|
+
* The `<name>` before `@` is read as-is rather than restricted to a
|
|
51
|
+
* hardcoded `npm`/`pnpm` allow-list — this is what `docouture new` ever
|
|
52
|
+
* writes there in practice, but nothing here depends on that being true.
|
|
53
|
+
*/
|
|
54
|
+
export function checkPackageManagerAvailable(packageManagerField) {
|
|
55
|
+
const label = 'package manager';
|
|
56
|
+
if (!packageManagerField) {
|
|
57
|
+
return Promise.resolve({
|
|
58
|
+
ok: true,
|
|
59
|
+
label,
|
|
60
|
+
message: 'no packageManager field declared in package.json — skipping',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const match = /^([^@\s]+)@/.exec(packageManagerField);
|
|
64
|
+
const pm = match?.[1];
|
|
65
|
+
if (!pm) {
|
|
66
|
+
return Promise.resolve({
|
|
67
|
+
ok: true,
|
|
68
|
+
label,
|
|
69
|
+
message: `could not parse packageManager '${packageManagerField}' — skipping`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return new Promise((resolvePromise) => {
|
|
73
|
+
execFile(pm, ['--version'], { timeout: 10_000 }, (err, stdout) => {
|
|
74
|
+
resolvePromise(err
|
|
75
|
+
? {
|
|
76
|
+
ok: false,
|
|
77
|
+
label,
|
|
78
|
+
message: `'${pm}' is not available on PATH`,
|
|
79
|
+
detail: `package.json declares packageManager: '${packageManagerField}' — install ${pm} (or run via corepack)`,
|
|
80
|
+
}
|
|
81
|
+
: { ok: true, label, message: `${pm} ${stdout.trim()} is available` });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
40
85
|
/**
|
|
41
86
|
* The four names the docs-site-package skill documents as having to agree,
|
|
42
87
|
* or a site builds to zero pages, or dies on "start page not found" — see
|
|
@@ -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
|
|
@@ -24,7 +24,11 @@ on:
|
|
|
24
24
|
branches: ['__DOCOUTURE_RELEASE_BRANCH__*']
|
|
25
25
|
|
|
26
26
|
concurrency:
|
|
27
|
-
|
|
27
|
+
# Scoped per-PR, not just per-workflow: a bare 'release-preview' group name
|
|
28
|
+
# collides with this monorepo's own code-release_preview.yml, so a label
|
|
29
|
+
# change firing both workflows at once could have one cancel the other
|
|
30
|
+
# mid-run even though they preview unrelated releases.
|
|
31
|
+
group: docouture-release-preview-${{ github.event.pull_request.number }}
|
|
28
32
|
cancel-in-progress: true
|
|
29
33
|
|
|
30
34
|
jobs:
|
|
@@ -148,6 +152,20 @@ jobs:
|
|
|
148
152
|
exit 0
|
|
149
153
|
fi
|
|
150
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
|
+
|
|
151
169
|
MESSAGE="
|
|
152
170
|
### :rocket: Docs Release Preview
|
|
153
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": "
|
|
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",
|