kaloko 0.5.2 → 0.7.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/README.md +97 -6
- package/package.json +1 -1
- package/schemas/changelog.v1.json +83 -0
- package/schemas/run.v1.json +19 -0
- package/schemas/scenario.v1.json +6 -1
- package/skills/kaloko/SKILL.md +35 -13
- package/skills/kaloko/reference/design.md +55 -0
- package/skills/kaloko/reference/docs.md +130 -4
- package/skills/kaloko/reference/integrations.md +67 -0
- package/skills/kaloko/reference/mcp.md +8 -1
- package/src/cli.mjs +241 -11
- package/src/lib/changelog.mjs +144 -8
- package/src/lib/ci.mjs +71 -0
- package/src/lib/design-cli.mjs +22 -2
- package/src/lib/design-notes.mjs +191 -0
- package/src/lib/docs-core.mjs +450 -0
- package/src/lib/docs-export.mjs +271 -0
- package/src/lib/docs.mjs +221 -0
- package/src/lib/figma.mjs +584 -0
- package/src/lib/git.mjs +23 -3
- package/src/lib/integrations-cli.mjs +211 -0
- package/src/lib/keyboard.mjs +42 -12
- package/src/lib/onboarding.mjs +3 -1
- package/src/lib/screens-cli.mjs +36 -0
- package/src/lib/service.mjs +20 -1
- package/viewer/index.html +580 -90
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Jira, Linear, channels and hosted runs
|
|
2
|
+
|
|
3
|
+
What the organization connected on kaloko.app (Settings → Integrations, done by an
|
|
4
|
+
admin) and what you may do with it from the CLI and MCP.
|
|
5
|
+
|
|
6
|
+
## Issues in Jira or Linear
|
|
7
|
+
|
|
8
|
+
When a reviewer returned a step or a criterion fails and the team tracks work in
|
|
9
|
+
Jira or Linear, file it there from the step instead of copying text by hand:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
kaloko issue create --step checkout --to jira --criterion AC2 --note "Total ignores the voucher"
|
|
13
|
+
kaloko issue list --state open
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `--run <id>` names the shared run; without it the current run is used once it
|
|
17
|
+
is shared. MCP: `create_issue`, `list_issues`.
|
|
18
|
+
- The issue gets the screenshot, the criterion with its evidence, the reviewers'
|
|
19
|
+
open notes on the step and a link back to the step. The project or team comes
|
|
20
|
+
from the admin's mapping per Kaloko project; `--target WEB` overrides it.
|
|
21
|
+
- The returned note on the step (or a new comment) mirrors the issue: when the
|
|
22
|
+
issue is done or cancelled in the tracker, the thread gets a reply and is
|
|
23
|
+
resolved; reopened, it opens again. Do not resolve that thread yourself.
|
|
24
|
+
- Needs the Developer / Tester role and a token with the comment scope. A 409
|
|
25
|
+
means the tracker is not connected or the project has no mapping: tell the
|
|
26
|
+
person, an admin sets it up.
|
|
27
|
+
- Name the issue key in the branch (`feat/WEB-12-gift-cards`, Linear's
|
|
28
|
+
`anna/eng-42-…`) or the pull request title, or `issue: { key: WEB-12 }` in the
|
|
29
|
+
scenario: the run links to the issue and the issue gets a comment with the run.
|
|
30
|
+
|
|
31
|
+
Ask before filing issues in bulk; one issue per real problem, not per criterion.
|
|
32
|
+
|
|
33
|
+
## Channels per project
|
|
34
|
+
|
|
35
|
+
Slack and Teams channels are chosen per project by an admin, with the events
|
|
36
|
+
each wants (new run, returned, accepted, comment, mention, release recorded) and
|
|
37
|
+
quiet hours. Nothing to do from the CLI: sharing, returning and recording a
|
|
38
|
+
release reach the right channels. Restricted projects reach only channels that
|
|
39
|
+
name them.
|
|
40
|
+
|
|
41
|
+
## Hosted runs
|
|
42
|
+
|
|
43
|
+
Kaloko can open a read-only scenario's pages on its own browsers every day or
|
|
44
|
+
week and share the run like yours (Business plan, a monthly page budget):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
kaloko schedule add --scenario qa/flows/public.yml --env production --every day --at 5
|
|
48
|
+
kaloko schedule add --scenario qa/flows/account.yml --env staging --every week --weekday mon --account anna
|
|
49
|
+
kaloko schedule list # next and last run, pages used this month
|
|
50
|
+
kaloko schedule run <id> # starts within 15 minutes
|
|
51
|
+
kaloko schedule pause|resume|remove <id>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Works for: public pages, pages behind HTTP Basic (the environment's
|
|
55
|
+
`access.basic` variables are sent once and stored encrypted), and signed-in
|
|
56
|
+
pages through a session saved with `kaloko auth save` (`--account`, cookies only).
|
|
57
|
+
- Opens only steps whose script just opens the page (`steps/open.mjs`, or a
|
|
58
|
+
script that only calls `page.goto`). Steps that click or type are left out and
|
|
59
|
+
named in the answer. Criteria decided: `exists`, `absent`, `count`, `text_*`,
|
|
60
|
+
`attr_*`, `title_matches`, `meta_matches`; the rest shows "not evaluated".
|
|
61
|
+
- Does not work for: native apps, local or private addresses (localhost, IPs,
|
|
62
|
+
`.local`), header credentials, base URLs per locale.
|
|
63
|
+
- For those, or for the full walk on a schedule, write a GitHub Actions
|
|
64
|
+
workflow instead: `kaloko schedule export github --scenario <file> --env <name>
|
|
65
|
+
[--every week] [--at 5]`. It names the repository secrets it needs.
|
|
66
|
+
- A 404 from `schedule add` means hosted runs are not enabled on this service:
|
|
67
|
+
use the workflow.
|
|
@@ -33,12 +33,14 @@ file.
|
|
|
33
33
|
| `decide_step` | records your (the agent's) decision on a step: `approve`, or `return` with a `note`; marked as an agent's and not counted as a person's. Never use it to approve for a person |
|
|
34
34
|
| `revoke_carried_approval` | revokes an approval carried over to a step that looks unchanged but should be looked at again (`run_id`, `step`) |
|
|
35
35
|
| `get_step_assets` | documentation layer (Business): newest capture of every step of a scenario with stable image, HTML and metadata URLs (`scenario`, `step`, `locale`, `viewport`, `accepted`) |
|
|
36
|
+
| `list_products` | the products the caller reads: name, modules, the projects it maps that the caller sees, the newest release, published docs (versions, languages), who reads it (and the public link), whether the caller manages it, links to its page, changelog and docs |
|
|
36
37
|
| `get_changelog` | the visual changelog of a product (`product`, `version`, `resolve: true` for the pictures, `limit`): releases newest first with the entry as written and its groups; see [docs.md](docs.md) |
|
|
37
38
|
| `draft_changelog_entry` | what accepted runs since the last release (or `since`) show differently, as a Keep a Changelog draft with before/after step references (`product`, `since`) |
|
|
38
39
|
| `resolve_step_image` | one step reference (`ref` = `kaloko:<scenario>/<step>?…`, `product`, `version`, `locale`, `viewport`) as a signed image URL with its run, how the version was found and canvas links |
|
|
39
40
|
| `list_step_changes` | documentation layer: steps whose capture or status changed since the previous accepted run or `since` (`scenario`, `since`, `accepted`); see [documentation.md](documentation.md) |
|
|
40
41
|
| `get_started` | where the organization stands (`empty`, `sample`, `active`), its sample run, the next steps and a prompt; call it in a new project, see [first-run.md](first-run.md) |
|
|
41
42
|
| `add_sample_run` | adds the sample run (or returns the one there is); needs an upload or full token and the Developer / Tester role |
|
|
43
|
+
| `search_screens` | the screen library: newest screenshot of every step across the organization's flows (`q` words matched against titles, ids, paths, URLs, page title and H1; `project`, `product`, `module`, `scenario`, `locale`, `viewport`, `device`, `status`, `environment`; `source: design` or `all` for design versions; `group: none` for every locale and viewport; `cursor` from `next`), each with a signed image and the canvas link |
|
|
42
44
|
| `get_affected_steps` | which steps of a scenario changed files reach, with the reason per step (`scenario`, `changed_files`, `run_id`, `upstream`); route files are matched against the step paths and URLs of the newest run. Imports and `affects:` globs need the repository: `kaloko affected` follows them |
|
|
43
45
|
|
|
44
46
|
## Comment tools
|
|
@@ -52,6 +54,9 @@ file.
|
|
|
52
54
|
| `get_signoff_record` | the signed acceptance record of an accepted run (Business): approvals with provenance, criteria, capture hashes, signature |
|
|
53
55
|
| `resolve_comment` | close a thread with a `note` that says what changed (it becomes the last reply) |
|
|
54
56
|
| `reopen_comment`, `edit_comment`, `delete_comment` | reopen; edit or delete your own comment |
|
|
57
|
+
| `create_issue` | a Jira or Linear issue from a step (`run_id`, `step`, `to`: jira or linear, optional `criterion`, `title`, `note`, `locale`, `viewport`, `target`): screenshot, criterion, notes and a link back; the step's thread resolves when the issue is done. Developer / Tester, scope comment; see [integrations.md](integrations.md) |
|
|
58
|
+
| `list_issues` | issues created from steps with key, URL and state (`run_id`, `scenario`, `state`) |
|
|
59
|
+
| `list_schedules`, `run_schedule` | hosted runs: schedules of read-only scenarios on Kaloko's browsers with the month's page budget; start one now (`id`) |
|
|
55
60
|
|
|
56
61
|
Writes need a token with scope full. Follow *Processing feedback* in
|
|
57
62
|
[collaboration.md](collaboration.md).
|
|
@@ -79,6 +84,8 @@ The same data and permissions as the REST API; each tool is its REST route.
|
|
|
79
84
|
| `list_step_changes` | steps that differ between two references (default design reference → draft) |
|
|
80
85
|
| `get_references`, `get_tokens` | design reference and baseline; the project's token set |
|
|
81
86
|
| `get_draft_feedback` | draft comments, pinned ones with their element |
|
|
82
|
-
| `
|
|
87
|
+
| `get_version_notes` | what changed in a version (`version`: vN or run id) or what publishing the draft now would say (`version: draft`): a summary and one plain sentence per changed step; text by default, `format: json` for the structure |
|
|
88
|
+
| `publish_version`, `set_reference`, `set_step_status`, `add_draft_comment` | writes: need a token with scope full; ask first where the skill says so. `publish_version` takes `notes` (a summary, then `step-id: text` lines, or `{ summary, steps }`) |
|
|
89
|
+
| `set_version_notes` | rewrite the summary or step texts of a published version's notes (design scope) |
|
|
83
90
|
|
|
84
91
|
Files are not uploaded over MCP: the CLI (`kaloko sync`) or the REST blob endpoints carry them.
|
package/src/cli.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import { upsertPrComment } from './lib/pr.mjs'
|
|
|
36
36
|
import { formatReviewQueue, shareReviewLine } from './lib/review.mjs'
|
|
37
37
|
import { htmlViewAllowed } from './lib/htmlview.mjs'
|
|
38
38
|
import { service, serviceUrl } from './lib/service.mjs'
|
|
39
|
+
import { issueCmd, scheduleCmd } from './lib/integrations-cli.mjs'
|
|
39
40
|
import { login } from './lib/login.mjs'
|
|
40
41
|
import { initNextSteps, loginNextSteps } from './lib/onboarding.mjs'
|
|
41
42
|
import { specCmd, driftCmd } from './lib/spec-cli.mjs'
|
|
@@ -45,7 +46,8 @@ import { formatAffected, selectedIds } from './lib/affected-core.mjs'
|
|
|
45
46
|
import { healStep, formatHeal } from './lib/heal.mjs'
|
|
46
47
|
import { readSource, stepFor, viewportFor, adhocScenario, writeImported, attachImported, keepVideos, slug } from './lib/import.mjs'
|
|
47
48
|
import { findPreview, pullRequestOf, envLines, writeEnvFile, githubWorkflow, gitlabJob, addPreviewEnvironment } from './lib/ci.mjs'
|
|
48
|
-
import { walkDesign, syncDesign, publishVersion, historyCmd, referenceCmd, tokensCmd, exportLive, localRevisions, branchCmd, uploadBlobs } from './lib/design-cli.mjs'
|
|
49
|
+
import { walkDesign, syncDesign, publishVersion, previewNotes, historyCmd, referenceCmd, tokensCmd, exportLive, localRevisions, branchCmd, uploadBlobs } from './lib/design-cli.mjs'
|
|
50
|
+
import { screensCmd } from './lib/screens-cli.mjs'
|
|
49
51
|
import { designDir, readBase, headKey } from './lib/design.mjs'
|
|
50
52
|
import { lintDesign, formatLint } from './lib/design-lint.mjs'
|
|
51
53
|
import { exportDesign } from './lib/design-export.mjs'
|
|
@@ -73,12 +75,17 @@ Project
|
|
|
73
75
|
Create ${CONFIG_FILE} and install the kaloko skill for your agent; detects the
|
|
74
76
|
framework, dev server URL, locales and main routes, and drafts a first scenario
|
|
75
77
|
(qa/flows/main-pages.yml) when the project has none; asks "Keep a visual changelog?"
|
|
76
|
-
(--docs: changelog adds CHANGELOG.md and the config block, full also docs/ folders
|
|
78
|
+
(--docs: changelog adds CHANGELOG.md and the config block, full also docs/ folders
|
|
79
|
+
with Diátaxis templates: tutorials, how-to, reference, explanation)
|
|
77
80
|
ci github|gitlab [--scenario a.yml,b.yml] [--env preview] [--trigger deployment|pull_request] [--no-affected]
|
|
78
81
|
[--out file] [--print] [--force]
|
|
79
82
|
Write a workflow that walks what each pull request affects on its preview
|
|
80
83
|
deployment (Vercel, Netlify, Cloudflare Pages, review apps) and comments on it;
|
|
81
84
|
adds the preview environment (base_url \${KALOKO_PREVIEW_URL}) to the config
|
|
85
|
+
ci github --release [--snippet] [--print] [--force]
|
|
86
|
+
Write .github/workflows/kaloko-release.yml: when release-please, changesets or
|
|
87
|
+
semantic-release publishes a GitHub release, kaloko release records it on Kaloko
|
|
88
|
+
(--snippet prints the step to add after the tool's own step instead)
|
|
82
89
|
ci preview-url [--wait 600] [--github-env] [--dotenv file]
|
|
83
90
|
In CI: the preview URL (KALOKO_PREVIEW_URL, the deployment event, provider
|
|
84
91
|
variables or the commit's GitHub deployments), the pull request and its base
|
|
@@ -228,21 +235,68 @@ Results
|
|
|
228
235
|
stability --pull [--scenario id] [--json]
|
|
229
236
|
Regions people drew on screenshots on kaloko.app (step detail → Page → Draw a
|
|
230
237
|
region) go into the scenario's ignore: of their steps; walk them again after
|
|
238
|
+
screens [words] [--project p] [--product p] [--module m] [--scenario id] [--locale l] [--viewport v] [--device d]
|
|
239
|
+
[--status pass|partial|fail|skip] [--environment e] [--designs] [--all] [--json] [--cursor c]
|
|
240
|
+
The screen library: the newest screenshot of every step across your flows, searched
|
|
241
|
+
by title, text, URL or id, with the canvas link of each (--designs adds design versions)
|
|
231
242
|
compare [--run id] [--baseline id | --design] [--json]
|
|
232
243
|
Text diff of two runs: step statuses, criterion verdicts, scores and evidence
|
|
233
244
|
(default: current run against the scenario baseline, else the previous run)
|
|
234
245
|
|
|
246
|
+
Jira and Linear (connected by an admin in Settings → Integrations)
|
|
247
|
+
issue create --step <id> --to jira|linear [--criterion AC2] [--title "…"] [--note "…"] [--run id] [--json]
|
|
248
|
+
An issue from a returned step or a failing criterion: the screenshot, the
|
|
249
|
+
criterion, the reviewers' notes and a link back; the step's comment is resolved
|
|
250
|
+
when the issue is done. Branches and PR titles that name a key (WEB-12) link runs
|
|
251
|
+
issue list [--run id] [--scenario id] [--state open|done|canceled] [--json]
|
|
252
|
+
|
|
253
|
+
Hosted runs (read-only scenarios on Kaloko's browsers, Business plan)
|
|
254
|
+
schedule add --scenario <file> --env <name> [--every day|week|manual] [--at 5] [--weekday mon] [--account key]
|
|
255
|
+
Kaloko opens the scenario's pages every day or week (hour in UTC) and shares the
|
|
256
|
+
run as you: public pages, HTTP Basic from the environment, or a saved session
|
|
257
|
+
(--account). Steps that click or type are left out; they need the full walk
|
|
258
|
+
schedule list | run <id> | pause <id> | resume <id> | remove <id> [--json]
|
|
259
|
+
schedule export github --scenario <file> --env <name> [--every day|week] [--at 5] [--out file] [--print]
|
|
260
|
+
A GitHub Actions workflow that walks the scenario on a schedule with the full CLI
|
|
261
|
+
|
|
262
|
+
Products (products: in kaloko.config.yml; without it the project is the product)
|
|
263
|
+
products sync [--product p] [--dry-run] [--json]
|
|
264
|
+
Put the products of the config on Kaloko: name, projects, modules, version
|
|
265
|
+
source, reference environment and default scenario, without recording a
|
|
266
|
+
release. They show under Products in the app with their changelog and docs;
|
|
267
|
+
publish: is applied only for an admin of the product's projects
|
|
268
|
+
products list [--json] The products on Kaloko you read: newest release, docs, who reads them, and
|
|
269
|
+
the products of the config that are not on Kaloko yet
|
|
270
|
+
|
|
235
271
|
Changelog (Keep a Changelog in CHANGELOG.md; pictures are step references kaloko:<scenario>/<step>)
|
|
236
272
|
changelog draft [--product p] [--since <tag|version>] [--apply] [--json] [--offline]
|
|
237
273
|
The Unreleased entry since the last release: merged pull requests and commits
|
|
238
274
|
(Conventional Commits when used, gh for titles) grouped Added / Changed / Fixed /
|
|
239
275
|
Removed, plus before/after pictures of the screens accepted runs show differently;
|
|
240
276
|
printed, or --apply writes it into CHANGELOG.md (not when a release tool owns it)
|
|
241
|
-
release [<version>] [--product p] [--date YYYY-MM-DD] [--runs id,id] [--no-write] [--dry-run] [--json]
|
|
277
|
+
release [<version>] [--product p] [--date YYYY-MM-DD] [--runs id,id] [--no-write] [--no-docs] [--dry-run] [--json]
|
|
242
278
|
Record a release on Kaloko: the version (default: from git tags, package.json,
|
|
243
279
|
release-please or changesets), its CHANGELOG.md entry (Unreleased becomes the
|
|
244
280
|
version unless --no-write) and the accepted runs that are its pictures; prints the
|
|
245
|
-
illustrated changelog page
|
|
281
|
+
illustrated changelog page. The docs folder is published as that version's docs
|
|
282
|
+
(Business) unless --no-docs
|
|
283
|
+
|
|
284
|
+
Docs (Markdown in docs/ with step references; Diátaxis folders; translations as guide.cs.md next to guide.md)
|
|
285
|
+
docs publish [--product p] [--version latest|<v>] [--reviewed] [--dry-run] [--json]
|
|
286
|
+
Send the docs folder to Kaloko as the docs of a version (default latest): readers
|
|
287
|
+
get a version selector, language and screen switches, search and training mode;
|
|
288
|
+
--reviewed says the text was checked, which clears its stale notes
|
|
289
|
+
docs check [--product p] [--version v] [--offline] [--strict] [--json]
|
|
290
|
+
Broken or unknown step references, stale sections (the screen changed since the
|
|
291
|
+
text was published), missing or outdated translations and user-visible changelog
|
|
292
|
+
items without a picture; exit 1 on references or stale sections (--strict: on
|
|
293
|
+
anything)
|
|
294
|
+
export --format md|docusaurus|vitepress|mkdocs|html|pdf|json [--product p] [--version v] [--lang l]
|
|
295
|
+
[--what all|docs|changelog] [--local] [--out dir|file]
|
|
296
|
+
The docs and changelog with their pictures as files: Markdown with an assets
|
|
297
|
+
folder, a Docusaurus, VitePress or MkDocs folder with sidebars, offline HTML, PDF
|
|
298
|
+
(printed by the browser) or the changelog as JSON; --local exports the files in
|
|
299
|
+
the repository instead of the published ones
|
|
246
300
|
|
|
247
301
|
Design (environments with kind: design; live HTML steps in a folder)
|
|
248
302
|
start --scenario <file> --env <design env> A run of the design folder (same scenario as development and QA)
|
|
@@ -254,7 +308,15 @@ Design (environments with kind: design; live HTML steps in a folder)
|
|
|
254
308
|
design lint [--env e | --dir design/] [--scenario file] [--all] [--strict] [--json]
|
|
255
309
|
Check the folder against the portable HTML rules (relative URLs, no CDNs, no storage,
|
|
256
310
|
deterministic content, viewport, lang, links that also work from disk); --strict fails on warnings
|
|
257
|
-
share [--name <text>] [--branch b]
|
|
311
|
+
share [--name <text>] [--branch b] [--notes file|-] [--notes-preview]
|
|
312
|
+
Publish the draft as a version of the flow (a run): "v4 · 3 changed, 17 unchanged",
|
|
313
|
+
with "what changed" notes per step (screens per viewport, text, tokens); --notes-preview
|
|
314
|
+
prints them without publishing, --notes replaces them (a summary, then "step: text" lines)
|
|
315
|
+
design import figma <file-url|key|export folder> [--scenario file] [--frames a,b] [--page p] [--map frames.json]
|
|
316
|
+
[--token-env FIGMA_TOKEN] [--tokens auto|write|skip] [--force] [--dry-run] [--publish [--notes file]]
|
|
317
|
+
Figma frames become design steps (frame PNG at 1× and 2×, text layers with their
|
|
318
|
+
typography, variables or styles as tokens.json) matched to steps by name or --map;
|
|
319
|
+
a folder exported from Figma works without a token. One way: no prototype links
|
|
258
320
|
history [--scenario id] [--step id] [--locale l] [--json]
|
|
259
321
|
Versions of the flow; revisions of a step with who (person or agent) and when
|
|
260
322
|
spec [step] [--ref design|draft|vN|<run id>] [--locale l] [--viewport v] [--local] [--json]
|
|
@@ -394,6 +456,49 @@ function stepOf (scenario, id) {
|
|
|
394
456
|
}
|
|
395
457
|
|
|
396
458
|
/** Scenario of --scenario (id or file) or of the current run. */
|
|
459
|
+
/**
|
|
460
|
+
* kaloko design import figma <file-url|key|folder>: Figma frames (or a folder exported from Figma) become the steps of
|
|
461
|
+
* the design folder; --publish then walks them and publishes a version (the design reference to be).
|
|
462
|
+
*/
|
|
463
|
+
async function designImport (args) {
|
|
464
|
+
const [, kind, source] = args._
|
|
465
|
+
if (kind !== 'figma' || !source) throw new UserError('Usage: kaloko design import figma <file-url|key|export folder> [--scenario file] [--env <design env>] [--frames a,b] [--page name] [--map frames.json|yml] [--token-env FIGMA_TOKEN] [--tokens auto|write|skip] [--force] [--dry-run] [--publish [--notes file]]')
|
|
466
|
+
const { config, root } = await context(args)
|
|
467
|
+
let run = null
|
|
468
|
+
try { run = await resolveRun({ root, output: config.output, run: args.run }) } catch {}
|
|
469
|
+
const runEnv = run ? environment(config, run.meta.environment.name) : null
|
|
470
|
+
const env = typeof args.env === 'string' ? environment(config, args.env) : runEnv?.kind === 'design' ? runEnv : Object.keys(config.environments).map((n) => environment(config, n)).find((e) => e.kind === 'design')
|
|
471
|
+
if (!env || env.kind !== 'design') throw new UserError('No design environment: add one with kind: design and serve: design/ to kaloko.config.yml, or pass --env <design env>.')
|
|
472
|
+
const scenarioFile = typeof args.scenario === 'string' ? resolve(args.scenario) : run && runEnv?.kind === 'design' ? resolve(root, run.meta.scenario_file) : null
|
|
473
|
+
if (!scenarioFile) throw new UserError('Name the scenario whose steps the frames are: --scenario <file>.')
|
|
474
|
+
const scenario = await loadScenario(scenarioFile)
|
|
475
|
+
const { importFigma, formatImport, readMap } = await import('./lib/figma.mjs')
|
|
476
|
+
const scope = startScope({ args: {}, scenario, config, env })
|
|
477
|
+
const viewports = Object.fromEntries(baseViewports(scope).map((v) => [v, scope.viewports[v]]))
|
|
478
|
+
const tokenVar = typeof args['token-env'] === 'string' ? args['token-env'] : typeof args.token === 'string' && /^[A-Z_][A-Z0-9_]*$/.test(args.token) ? args.token : 'FIGMA_TOKEN'
|
|
479
|
+
const dir = typeof args.dir === 'string' ? resolve(args.dir) : designDir(env, root)
|
|
480
|
+
const r = await importFigma({
|
|
481
|
+
source, scenario, viewports, locales: scenario.locales || ['default'], dir,
|
|
482
|
+
token: process.env[tokenVar] || (tokenVar === 'FIGMA_TOKEN' ? process.env.FIGMA_ACCESS_TOKEN : null) || null,
|
|
483
|
+
map: typeof args.map === 'string' ? await readMap(resolve(args.map)) : null, only: list(args.frames), page: typeof args.page === 'string' ? args.page : null,
|
|
484
|
+
tokensMode: ['write', 'skip'].includes(args.tokens) ? args.tokens : 'auto', force: !!args.force, dryRun: !!args['dry-run'], say
|
|
485
|
+
})
|
|
486
|
+
if (args.json) say(JSON.stringify(r, null, 2))
|
|
487
|
+
else say(formatImport(r, { dir: relative(process.cwd(), dir) || '.' }))
|
|
488
|
+
if (args['dry-run']) return r
|
|
489
|
+
const lint = await lintDesign({ dir, scenario })
|
|
490
|
+
if (lint.errors) say(`\n${formatLint(lint, { max: 10 })}`)
|
|
491
|
+
if (!args.publish) {
|
|
492
|
+
say(`\nNext: kaloko start --scenario ${relative(process.cwd(), scenarioFile)} --env ${env.name} && kaloko walk && kaloko share (or rerun with --publish).\nAccept the version on the canvas and set it as the design reference (kaloko reference --design --version <n>): implementation runs with packs: [design] are then checked against these frames.`)
|
|
493
|
+
return r
|
|
494
|
+
}
|
|
495
|
+
await commands.start({ ...args, _: [], scenario: scenarioFile, env: env.name })
|
|
496
|
+
const current = await resolveRun({ root, output: config.output })
|
|
497
|
+
await commands.walk({ _: [], run: current.dir })
|
|
498
|
+
await commands.share({ _: [], run: current.dir, ...(typeof args.notes === 'string' ? { notes: args.notes } : {}), ...(typeof args.name === 'string' ? { name: args.name } : {}), ...(typeof args.branch === 'string' ? { branch: args.branch } : {}) })
|
|
499
|
+
return r
|
|
500
|
+
}
|
|
501
|
+
|
|
397
502
|
async function scenarioIdOf (args, config, root) {
|
|
398
503
|
if (typeof args.scenario === 'string') {
|
|
399
504
|
if (/\.ya?ml$/.test(args.scenario)) return (await loadScenario(resolve(args.scenario))).id
|
|
@@ -801,7 +906,7 @@ const commands = {
|
|
|
801
906
|
const cfgFile = existsSync(file) ? file : join(cwd, LEGACY_CONFIG_FILE)
|
|
802
907
|
const cfg = await readFile(cfgFile, 'utf8')
|
|
803
908
|
const products = /^products:/m.test(cfg) ? [] : await detectProducts(cwd)
|
|
804
|
-
if (!/^docs:/m.test(cfg) && !/^products:/m.test(cfg)) { await writeFile(cfgFile, `${cfg.replace(/\n*$/, '\n')}${docsConfigBlock({ products })}`); say(`Added the changelog settings to ${relative(cwd, cfgFile)}${products.length ? ` with ${products.length} products (${products.map((p) => p.slug).join(', ')})` : ''}`) }
|
|
909
|
+
if (!/^docs:/m.test(cfg) && !/^products:/m.test(cfg)) { await writeFile(cfgFile, `${cfg.replace(/\n*$/, '\n')}${docsConfigBlock({ products, full: docsMode === 'full' })}`); say(`Added the changelog settings to ${relative(cwd, cfgFile)}${products.length ? ` with ${products.length} products (${products.map((p) => p.slug).join(', ')})` : ''}`) }
|
|
805
910
|
for (const f of products.length ? products.map((p) => join(cwd, p.dir, 'CHANGELOG.md')) : [join(cwd, 'CHANGELOG.md')]) {
|
|
806
911
|
if (existsSync(f)) continue
|
|
807
912
|
await mkdirp(dirname(f), { recursive: true })
|
|
@@ -815,7 +920,7 @@ const commands = {
|
|
|
815
920
|
await mkdirp(dirname(f), { recursive: true })
|
|
816
921
|
await writeFile(f, text)
|
|
817
922
|
}
|
|
818
|
-
say('Docs folders in docs/ (tutorials, how-to, reference, explanation) with an index')
|
|
923
|
+
say('Docs folders in docs/ (tutorials, how-to, reference, explanation) with an index and a template in each; kaloko docs publish sends them, kaloko docs check checks them')
|
|
819
924
|
}
|
|
820
925
|
} else if (!docsMode) say('Visual changelog: kaloko init --docs=changelog adds CHANGELOG.md and the config block (recommended).')
|
|
821
926
|
const agent = typeof args.agent === 'string' ? args.agent : 'none'
|
|
@@ -874,7 +979,7 @@ const commands = {
|
|
|
874
979
|
try { written = await readFile(existsSync(file) ? file : join(cwd, LEGACY_CONFIG_FILE), 'utf8') } catch {}
|
|
875
980
|
const org = typeof args.org === 'string' ? args.org : (written.match(/^org:\s*["']?([a-z0-9-]+)["']?\s*$/m) || [])[1]
|
|
876
981
|
const base = serviceUrl({ share: { url: (written.match(/^share:[ \t]*\n(?:[ \t]+.*\n)*?[ \t]+url:[ \t]*["']?(https?:\/\/[^\s"']+)/m) || [])[1] } })
|
|
877
|
-
say(initNextSteps({ base, org, loggedIn: !!process.env.KALOKO_TOKEN, devServer: detected && !running ? detected : null, scenario: first && relative(cwd, first) }).join('\n'))
|
|
982
|
+
say(initNextSteps({ base, org, loggedIn: !!process.env.KALOKO_TOKEN, devServer: detected && !running ? detected : null, scenario: first && relative(cwd, first), products: /^products:/m.test(written) }).join('\n'))
|
|
878
983
|
say(`Scenario template: ${join(packageRoot, 'templates', 'scenario.yml')}`)
|
|
879
984
|
},
|
|
880
985
|
|
|
@@ -920,7 +1025,7 @@ const commands = {
|
|
|
920
1025
|
const vpNames = baseViewports(scope)
|
|
921
1026
|
const git = await gitMeta(root)
|
|
922
1027
|
const executor = { kind: args.executor ? 'agent' : 'cli', name: args.executor || 'kaloko-cli' }
|
|
923
|
-
const { project, issue } = await projectAndIssue({ cwd: root, config, scenario, branch: git?.branch })
|
|
1028
|
+
const { project, issue } = await projectAndIssue({ cwd: root, config, scenario, branch: git?.branch, title: git?.pr?.title || null })
|
|
924
1029
|
const appVer = typeof args['app-version'] === 'string' ? args['app-version'].slice(0, 80) : await appVersion(root)
|
|
925
1030
|
// the product the flow documents (visual changelog): its tag, or the only product of the config
|
|
926
1031
|
const product = scenario.product || (Object.keys(config.products || {}).length === 1 ? Object.keys(config.products)[0] : null)
|
|
@@ -1390,8 +1495,32 @@ const commands = {
|
|
|
1390
1495
|
console.log(preview.url)
|
|
1391
1496
|
return
|
|
1392
1497
|
}
|
|
1393
|
-
if (!['github', 'gitlab'].includes(action)) throw new UserError('Usage: kaloko ci github|gitlab [--scenario a.yml,b.yml] [--env preview] [--trigger deployment|pull_request] [--no-affected] [--out file] [--print] [--force]\n kaloko ci preview-url [--wait 600] [--github-env] [--dotenv file]')
|
|
1498
|
+
if (!['github', 'gitlab'].includes(action)) throw new UserError('Usage: kaloko ci github|gitlab [--scenario a.yml,b.yml] [--env preview] [--trigger deployment|pull_request] [--no-affected] [--out file] [--print] [--force]\n kaloko ci github --release [--snippet] [--print] [--force]\n kaloko ci preview-url [--wait 600] [--github-env] [--dotenv file]')
|
|
1394
1499
|
const { config, root, file } = await context(args)
|
|
1500
|
+
if (args.release) {
|
|
1501
|
+
// release-tool integrations: record each published release on Kaloko (docs/DOCS-CHANGELOG.md)
|
|
1502
|
+
if (action !== 'github') throw new UserError('--release writes a GitHub Actions workflow; on GitLab run kaloko release <version> --no-write in your release job.')
|
|
1503
|
+
const { productsOf, detectVersionSource } = await import('./lib/changelog.mjs')
|
|
1504
|
+
const { releaseWorkflow, releaseSnippet } = await import('./lib/ci.mjs')
|
|
1505
|
+
const products = productsOf(config, root)
|
|
1506
|
+
const sources = await Promise.all(products.map((p) => detectVersionSource(root, p)))
|
|
1507
|
+
const tool = sources.map((s) => s.tool).find(Boolean) || null
|
|
1508
|
+
let pkg = null
|
|
1509
|
+
try { pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')) } catch {}
|
|
1510
|
+
const pm = existsSync(join(root, 'pnpm-lock.yaml')) ? 'pnpm' : existsSync(join(root, 'yarn.lock')) ? 'yarn' : existsSync(join(root, 'bun.lockb')) || existsSync(join(root, 'bun.lock')) ? 'bun' : 'npm'
|
|
1511
|
+
const dependency = !!(pkg && ({ ...pkg.dependencies, ...pkg.devDependencies }).kaloko)
|
|
1512
|
+
const version = `^${JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8')).version}`
|
|
1513
|
+
if (args.snippet) { say(releaseSnippet({ tool, pm, dependency, version, product: products.length > 1 ? '<product>' : null })); return }
|
|
1514
|
+
const content = releaseWorkflow({ products: products.map((p, i) => ({ slug: products.length > 1 || !p.implicit ? p.slug : null, pattern: sources[i].pattern || 'v*' })), pm, dependency, version, tool })
|
|
1515
|
+
if (args.print) { say(content); return }
|
|
1516
|
+
const out = resolve(root, typeof args.out === 'string' ? args.out : '.github/workflows/kaloko-release.yml')
|
|
1517
|
+
if (existsSync(out) && !args.force) throw new UserError(`${relative(process.cwd(), out)} exists; pass --force to replace it (or --print to compare).`)
|
|
1518
|
+
await mkdir(dirname(out), { recursive: true })
|
|
1519
|
+
await writeFile(out, content)
|
|
1520
|
+
say(`Wrote ${relative(process.cwd(), out)}: every published GitHub release${tool ? ` (${tool} publishes them)` : ''} is recorded on Kaloko with its changelog entry, pictures and docs.`)
|
|
1521
|
+
say('Next:\n add the repository secret KALOKO_TOKEN (tester role, upload scope)\n publish releases with your release tool as before; nothing else changes in your release workflow')
|
|
1522
|
+
return
|
|
1523
|
+
}
|
|
1395
1524
|
const envName = typeof args.env === 'string' ? args.env : 'preview'
|
|
1396
1525
|
const all = (await scenarioFiles(config, root)).map((f) => relative(root, f))
|
|
1397
1526
|
const scenarios = list(args.scenario) || all
|
|
@@ -1568,6 +1697,7 @@ const commands = {
|
|
|
1568
1697
|
if (env.kind === 'design') {
|
|
1569
1698
|
const r = await syncDesign({ config, root, run, scenario, env, client, args: { ...args, strict: true }, say, pull: false, render: (steps) => walkDesign({ config, root, run, scenario, env, pick: steps, locales: run.meta.scope.locales, viewports: run.meta.scope.viewports, say }).then(() => evaluateRun({ run, scenario, config, root })) })
|
|
1570
1699
|
if (r.conflicts) return
|
|
1700
|
+
if (args['notes-preview']) { await previewNotes({ config, scenario, client, args, say }); return }
|
|
1571
1701
|
const v = await publishVersion({ config, scenario, client, args, say })
|
|
1572
1702
|
const meta = await readJson(join(run.dir, 'meta.json'))
|
|
1573
1703
|
meta.shared = { url: v.url, at: new Date().toISOString(), version: v.version, run: v.id }
|
|
@@ -1663,7 +1793,8 @@ const commands = {
|
|
|
1663
1793
|
},
|
|
1664
1794
|
|
|
1665
1795
|
async design (args) {
|
|
1666
|
-
if (args._[0]
|
|
1796
|
+
if (args._[0] === 'import') return designImport(args)
|
|
1797
|
+
if (args._[0] !== 'lint') throw new UserError('Usage: kaloko design lint [--env <design env> | --dir design/] [--scenario file] [--all] [--strict] [--json]\n kaloko design import figma <file-url|key|export folder> [--frames a,b] [--map frames.json] [--token-env FIGMA_TOKEN] [--publish]')
|
|
1667
1798
|
const { config, root } = await context(args)
|
|
1668
1799
|
let run = null
|
|
1669
1800
|
try { run = await resolveRun({ root, output: config.output, run: args.run }) } catch {}
|
|
@@ -1680,6 +1811,12 @@ const commands = {
|
|
|
1680
1811
|
if (report.errors || (args.strict && report.warnings)) process.exitCode = 1
|
|
1681
1812
|
},
|
|
1682
1813
|
|
|
1814
|
+
/** `kaloko screens [words]`: the screen library of the organization (newest capture of every step). */
|
|
1815
|
+
async screens (args) {
|
|
1816
|
+
const { config } = await context(args)
|
|
1817
|
+
await screensCmd({ config, client: service(config), args, say })
|
|
1818
|
+
},
|
|
1819
|
+
|
|
1683
1820
|
async history (args) {
|
|
1684
1821
|
const { config, root } = await context(args)
|
|
1685
1822
|
const scenarioId = await scenarioIdOf(args, config, root)
|
|
@@ -1781,6 +1918,20 @@ const commands = {
|
|
|
1781
1918
|
* The signed acceptance record of an accepted run: JSON and PDF downloaded and checked against the service's public
|
|
1782
1919
|
* key; --verify checks a record someone handed over (no token, no project needed).
|
|
1783
1920
|
*/
|
|
1921
|
+
/** Jira and Linear (#21): an issue from a step of a shared run, and the issues created so far. */
|
|
1922
|
+
async issue (args) {
|
|
1923
|
+
const { config, root } = await context(args)
|
|
1924
|
+
let runId = null
|
|
1925
|
+
if (args._[0] === 'create' && typeof args.run !== 'string') { try { const r = await resolveRun({ root, output: config.output }); if (r.meta.shared) runId = r.meta.id } catch {} }
|
|
1926
|
+
return issueCmd({ args, config, client: service(config), runId, say })
|
|
1927
|
+
},
|
|
1928
|
+
|
|
1929
|
+
/** Hosted runs (#16): schedules of read-only scenarios on Kaloko's browsers, or a scheduled GitHub workflow. */
|
|
1930
|
+
async schedule (args) {
|
|
1931
|
+
const { config, root } = await context(args)
|
|
1932
|
+
return scheduleCmd({ args, config, root, say, deps: { loadScenario, environment, baseUrl, projectAndIssue, packageRoot, service } })
|
|
1933
|
+
},
|
|
1934
|
+
|
|
1784
1935
|
async signoff (args) {
|
|
1785
1936
|
if (typeof args.verify === 'string') {
|
|
1786
1937
|
const config = await context(args).then((c) => c.config).catch(() => ({}))
|
|
@@ -2039,6 +2190,27 @@ const commands = {
|
|
|
2039
2190
|
return
|
|
2040
2191
|
}
|
|
2041
2192
|
if (typeof args.scenario === 'string') return exportScenario({ args, config, root, client })
|
|
2193
|
+
if (typeof args.format === 'string') {
|
|
2194
|
+
// living docs and the changelog as files (docs/DOCS-CHANGELOG.md → Exports)
|
|
2195
|
+
if (!config.org) throw new UserError('Set org: in the config first.')
|
|
2196
|
+
const { pickProduct } = await import('./lib/changelog.mjs')
|
|
2197
|
+
const { exportDocs } = await import('./lib/docs.mjs')
|
|
2198
|
+
const product = pickProduct(config, root, args.product)
|
|
2199
|
+
const printPdf = async (htmlFile, outFile) => {
|
|
2200
|
+
const { chromium } = await import('playwright')
|
|
2201
|
+
const browser = await chromium.launch(config.browser?.channel ? { channel: config.browser.channel } : {}).catch(() => chromium.launch())
|
|
2202
|
+
try {
|
|
2203
|
+
const page = await browser.newPage()
|
|
2204
|
+
await page.goto(pathToFileURL(htmlFile).href, { waitUntil: 'load', timeout: 60000 })
|
|
2205
|
+
await page.pdf({ path: outFile, format: 'A4', printBackground: true, margin: { top: '16mm', bottom: '16mm', left: '14mm', right: '14mm' } })
|
|
2206
|
+
} finally { await browser.close() }
|
|
2207
|
+
}
|
|
2208
|
+
const r = await exportDocs({ root, config, product, client, format: args.format, version: typeof args.version === 'string' ? args.version : 'latest', lang: typeof args.lang === 'string' ? args.lang : null, what: ['docs', 'changelog'].includes(args.what) ? args.what : 'all', out: typeof args.out === 'string' ? resolve(args.out) : null, local: !!args.local, printPdf: args['server-pdf'] ? null : printPdf, say })
|
|
2209
|
+
if (r.kind === 'file') { say(`Exported ${product.slug} as ${r.format} → ${relative(process.cwd(), r.file) || r.file}`); return }
|
|
2210
|
+
const m = r.manifest
|
|
2211
|
+
say(`Exported ${product.slug} ${m?.version || ''} as ${r.format} → ${relative(process.cwd(), r.dir) || r.dir}\n ${m ? `${m.pages} page(s), ${m.releases} release(s), ${m.pictures} picture(s)` : `${r.files} file(s)`}${m?.missing?.length ? `\n ! no accepted picture for: ${m.missing.slice(0, 8).join(', ')}${m.missing.length > 8 ? ' …' : ''}` : ''}`)
|
|
2212
|
+
return
|
|
2213
|
+
}
|
|
2042
2214
|
let runId = args.run
|
|
2043
2215
|
if (!runId) { try { runId = (await resolveRun({ root, output: config.output })).meta.id } catch {} }
|
|
2044
2216
|
if (!runId || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9-]+$/.test(runId)) throw new UserError('--run <id> is required (a shared run id).')
|
|
@@ -2095,6 +2267,55 @@ const commands = {
|
|
|
2095
2267
|
say(draftText(d))
|
|
2096
2268
|
},
|
|
2097
2269
|
|
|
2270
|
+
/** `kaloko docs publish|check`: the docs folder on Kaloko per version, and what in it needs attention. */
|
|
2271
|
+
async docs (args) {
|
|
2272
|
+
const sub = args._[0]
|
|
2273
|
+
if (!['publish', 'check'].includes(sub)) throw new UserError('Usage: kaloko docs publish [--product p] [--version latest|<v>] [--reviewed] [--dry-run] [--json]\n kaloko docs check [--product p] [--version v] [--offline] [--strict] [--json]')
|
|
2274
|
+
const { config, root } = await context(args)
|
|
2275
|
+
const { pickProduct } = await import('./lib/changelog.mjs')
|
|
2276
|
+
const docs = await import('./lib/docs.mjs')
|
|
2277
|
+
const product = pickProduct(config, root, args.product)
|
|
2278
|
+
const version = typeof args.version === 'string' ? args.version : 'latest'
|
|
2279
|
+
if (sub === 'publish') {
|
|
2280
|
+
if (!config.org && !args['dry-run']) throw new UserError('Set org: in the config first.')
|
|
2281
|
+
const r = await docs.publishDocs({ root, config, product, client: args['dry-run'] ? null : service(config), version, reviewed: !!args.reviewed, dryRun: !!args['dry-run'] })
|
|
2282
|
+
if (args.json) { say(JSON.stringify(r, null, 2)); return }
|
|
2283
|
+
if (r.dry_run) { say(`${product.slug}: ${r.pages} page(s) from ${r.dir} would go to Kaloko as ${r.version} (${r.languages.join(', ')}); dry run, nothing sent.${r.broken.length ? `\n ! broken step references: ${r.broken.join('; ')}` : ''}`); return }
|
|
2284
|
+
const lines = [`${product.slug} docs ${r.version}: ${r.pages} page(s) from ${r.dir}, ${r.changed} changed, ${r.unchanged} unchanged${r.removed ? `, ${r.removed} removed` : ''}${args.reviewed ? ' (marked as checked)' : ''}`, ` ${r.url}`]
|
|
2285
|
+
for (const b of r.broken_refs || []) lines.push(` ! broken step reference in ${b.path || 'index'}${b.lang ? ` (${b.lang})` : ''}: ${b.ref}`)
|
|
2286
|
+
for (const u of (r.unresolved || []).slice(0, 10)) lines.push(` ! no accepted picture yet: ${u.ref} (${u.path || 'index'})`)
|
|
2287
|
+
for (const n of r.notes || []) lines.push(` ! ${n}`)
|
|
2288
|
+
say(lines.join('\n'))
|
|
2289
|
+
return
|
|
2290
|
+
}
|
|
2291
|
+
// check: the scenario files say which steps exist
|
|
2292
|
+
const scenarios = new Map()
|
|
2293
|
+
for (const f of await scenarioFiles(config, root).catch(() => [])) { try { const s = yaml.load(await readFile(f, 'utf8')) || {}; if (s.id) scenarios.set(String(s.id), new Set((s.nodes || []).map((n) => String(n.id)))) } catch {} }
|
|
2294
|
+
let client = null
|
|
2295
|
+
if (!args.offline) { try { client = service(config) } catch {} }
|
|
2296
|
+
const r = await docs.docsCheck({ root, config, product, client, scenarios: scenarios.size ? scenarios : null, version })
|
|
2297
|
+
say(args.json ? JSON.stringify(r, null, 2) : docs.formatCheck(r))
|
|
2298
|
+
if (!r.ok || (args.strict && r.warnings)) process.exitCode = 1
|
|
2299
|
+
},
|
|
2300
|
+
|
|
2301
|
+
/** `kaloko products sync|list`: the products of the config on Kaloko, without a release; what the service has. */
|
|
2302
|
+
async products (args) {
|
|
2303
|
+
const sub = args._[0]
|
|
2304
|
+
if (!['sync', 'list'].includes(sub)) throw new UserError('Usage: kaloko products sync [--product p] [--dry-run] [--json]\n kaloko products list [--json]')
|
|
2305
|
+
const { config, root } = await context(args)
|
|
2306
|
+
const lib = await import('./lib/changelog.mjs')
|
|
2307
|
+
if (sub === 'list') {
|
|
2308
|
+
if (!config.org) throw new UserError('Set org: in the config first.')
|
|
2309
|
+
const r = await lib.listProducts({ root, config, client: service(config) })
|
|
2310
|
+
say(args.json ? JSON.stringify(r, null, 2) : lib.formatList(r))
|
|
2311
|
+
return
|
|
2312
|
+
}
|
|
2313
|
+
if (!config.org && !args['dry-run']) throw new UserError('Set org: in the config first.')
|
|
2314
|
+
const r = await lib.syncProducts({ root, config, client: args['dry-run'] ? null : service(config), only: typeof args.product === 'string' ? args.product : null, dryRun: !!args['dry-run'] })
|
|
2315
|
+
say(args.json ? JSON.stringify(r, null, 2) : lib.formatSync(r))
|
|
2316
|
+
if (r.products.some((p) => p.ok === false)) process.exitCode = 1
|
|
2317
|
+
},
|
|
2318
|
+
|
|
2098
2319
|
/** `kaloko release <version>`: the version's changelog entry and its reference runs on the service. */
|
|
2099
2320
|
async release (args) {
|
|
2100
2321
|
const { config, root } = await context(args)
|
|
@@ -2103,6 +2324,14 @@ const commands = {
|
|
|
2103
2324
|
const product = pickProduct(config, root, args.product)
|
|
2104
2325
|
const client = args['dry-run'] ? null : service(config)
|
|
2105
2326
|
const r = await releaseProduct({ root, config, product, version: args._[0] || null, date: typeof args.date === 'string' ? args.date : null, client, write: !args['no-write'], runs: list(args.runs), dryRun: !!args['dry-run'] })
|
|
2327
|
+
// the docs of this version: what the docs folder says now (Business; latest stays as kaloko docs publish left it)
|
|
2328
|
+
if (r.release && !args['no-docs']) {
|
|
2329
|
+
const docs = await import('./lib/docs.mjs')
|
|
2330
|
+
const pages = await docs.collectDocs(root, product).then((x) => x.pages.length).catch(() => 0)
|
|
2331
|
+
if (pages) {
|
|
2332
|
+
try { r.docs = await docs.publishDocs({ root, config, product, client, version: r.version }) } catch (e) { r.docs = { error: String(e.message || e).split('\n')[0] } }
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2106
2335
|
if (args.json) { say(JSON.stringify(r, null, 2)); return }
|
|
2107
2336
|
const file = relative(process.cwd(), r.file) || r.file
|
|
2108
2337
|
const lines = [`${product.slug} ${r.version} (${r.date}) · version source: ${r.source.tool || r.source.source}${r.source.path ? ` (${r.source.path})` : ''}`]
|
|
@@ -2111,6 +2340,7 @@ const commands = {
|
|
|
2111
2340
|
lines.push(` ${r.release.created ? 'Recorded' : 'Updated'} on the service with ${r.release.runs.length} reference run(s)${r.release.versioned_runs ? `; ${r.release.versioned_runs} accepted run(s) now carry ${r.version}` : ''}`)
|
|
2112
2341
|
lines.push(` ${r.release.url}`)
|
|
2113
2342
|
} else lines.push(' Dry run: nothing sent.')
|
|
2343
|
+
if (r.docs) lines.push(r.docs.error ? ` ! Docs of ${r.version} not published: ${r.docs.error}` : ` Docs of ${r.version}: ${r.docs.pages} page(s)${r.docs.changed ? `, ${r.docs.changed} changed since the last publish of ${r.version}` : ''}`)
|
|
2114
2344
|
for (const n of r.notes) lines.push(` ! ${n}`)
|
|
2115
2345
|
if (r.finalized) lines.push(` Commit ${file} with the release.`)
|
|
2116
2346
|
say(lines.join('\n'))
|