@vizzly-testing/cli 0.39.0 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/cli.js +11 -2
- package/dist/commands/init.js +1 -3
- package/dist/init/agents.js +51 -10
- package/dist/reporter/reporter-bundle.iife.js +2 -2
- package/dist/reporter-ssr/ssr-entry.js +3 -6
- package/dist/utils/cli-update.js +105 -0
- package/dist/utils/update-check.js +2 -0
- package/dist/utils/update-notifications.js +175 -0
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -168,9 +168,9 @@ Install the Vizzly skill and a short `AGENTS.md` note:
|
|
|
168
168
|
pnpm exec vizzly init --agent-guidance
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
This adds `.agents/skills/vizzly` and a note in `AGENTS.md`, keeping
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
This adds `.agents/skills/vizzly` and a note in `AGENTS.md`, keeping your
|
|
172
|
+
other instructions. Use `--agent-skill` for just the skill. Run either command
|
|
173
|
+
again to refresh it without changing your config.
|
|
174
174
|
`--skip-agent-skill` skips agent setup unless you also request `--agent-guidance`.
|
|
175
175
|
|
|
176
176
|
Use `context` to read local results. For cloud results, check the API schema
|
|
@@ -301,6 +301,7 @@ See their setup guides above for details.
|
|
|
301
301
|
## More
|
|
302
302
|
|
|
303
303
|
[Full docs](https://docs.vizzly.dev) ·
|
|
304
|
+
[Updating Vizzly](docs/updates.md) ·
|
|
304
305
|
[Report a bug or suggest a change](https://github.com/vizzly-testing/cli/issues)
|
|
305
306
|
|
|
306
307
|
PRs welcome. MIT licensed.
|
package/dist/cli.js
CHANGED
|
@@ -29,12 +29,14 @@ import { createPluginServices } from './plugin-api.js';
|
|
|
29
29
|
import { loadPlugins } from './plugin-loader.js';
|
|
30
30
|
import { createServices } from './services/index.js';
|
|
31
31
|
import { withTimeout } from './utils/async-utils.js';
|
|
32
|
+
import { runUpdatedCli } from './utils/cli-update.js';
|
|
32
33
|
import { colors } from './utils/colors.js';
|
|
33
34
|
import { loadConfig } from './utils/config-loader.js';
|
|
34
35
|
import { getContext } from './utils/context.js';
|
|
35
36
|
import { saveUserPath } from './utils/global-config.js';
|
|
36
37
|
import * as output from './utils/output.js';
|
|
37
38
|
import { getPackageVersion } from './utils/package-info.js';
|
|
39
|
+
import { notifyUpdates } from './utils/update-notifications.js';
|
|
38
40
|
|
|
39
41
|
// Custom help formatting with BearDen design system
|
|
40
42
|
const formatHelp = (cmd, helper) => {
|
|
@@ -393,7 +395,7 @@ function writeCommanderError(text) {
|
|
|
393
395
|
}
|
|
394
396
|
}
|
|
395
397
|
}
|
|
396
|
-
program.name('vizzly').description('Vizzly CLI for visual regression testing').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output (shorthand for --log-level debug)').option('--log-level <level>', 'Log level: debug, info, warn, error (default: info, or VIZZLY_LOG_LEVEL env var)').option('--json [fields]', 'JSON output, optionally specify fields (e.g., --json id,status,branch)').option('--color', 'Force colored output (even in non-TTY)').option('--no-color', 'Disable colored output').option('--strict', 'Fail on any error (default: be resilient, warn on non-critical issues)').configureHelp({
|
|
398
|
+
program.name('vizzly').description('Vizzly CLI for visual regression testing').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output (shorthand for --log-level debug)').option('--log-level <level>', 'Log level: debug, info, warn, error (default: info, or VIZZLY_LOG_LEVEL env var)').option('--json [fields]', 'JSON output, optionally specify fields (e.g., --json id,status,branch)').option('--color', 'Force colored output (even in non-TTY)').option('--no-color', 'Disable colored output').option('--no-update-notifier', 'Skip update checks and notices').option('--strict', 'Fail on any error (default: be resilient, warn on non-critical issues)').configureHelp({
|
|
397
399
|
formatHelp
|
|
398
400
|
});
|
|
399
401
|
program.configureOutput({
|
|
@@ -402,6 +404,13 @@ program.configureOutput({
|
|
|
402
404
|
program.showHelpAfterError('(Run vizzly --help for available commands)');
|
|
403
405
|
program.showSuggestionAfterError();
|
|
404
406
|
program.exitOverride();
|
|
407
|
+
program.hook('preAction', async (_command, actionCommand) => {
|
|
408
|
+
if (actionCommand.name() === 'init') return;
|
|
409
|
+
let updatedCli = await notifyUpdates(actionCommand.optsWithGlobals());
|
|
410
|
+
if (!updatedCli) return;
|
|
411
|
+
let code = await runUpdatedCli(updatedCli, process.argv.slice(2), process.cwd());
|
|
412
|
+
process.exit(code);
|
|
413
|
+
});
|
|
405
414
|
|
|
406
415
|
// Load plugins before defining commands
|
|
407
416
|
// We need to manually parse to get the config option early
|
|
@@ -456,7 +465,7 @@ if (needsRuntimeBootstrap) {
|
|
|
456
465
|
output.debug(`Plugin loading failed: ${error.message}`);
|
|
457
466
|
}
|
|
458
467
|
}
|
|
459
|
-
program.command('init').description('Initialize Vizzly in your project').option('--quick', 'Create an editable config without the setup wizard').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install the Vizzly agent skill
|
|
468
|
+
program.command('init').description('Initialize Vizzly in your project').option('--quick', 'Create an editable config without the setup wizard').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install or refresh the Vizzly agent skill').option('--agent-guidance', 'Install or refresh the Vizzly skill and AGENTS.md guidance').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
|
|
460
469
|
let globalOptions = getGlobalOptions();
|
|
461
470
|
try {
|
|
462
471
|
let result = await init({
|
package/dist/commands/init.js
CHANGED
|
@@ -119,9 +119,7 @@ export async function init(options = {}) {
|
|
|
119
119
|
let prompts;
|
|
120
120
|
try {
|
|
121
121
|
if (initialized) {
|
|
122
|
-
plan =
|
|
123
|
-
changes: []
|
|
124
|
-
};
|
|
122
|
+
plan = await planAgentSetup(cwd, agentOptions(options));
|
|
125
123
|
} else if (quick) {
|
|
126
124
|
let configChange = planConfig(config);
|
|
127
125
|
let agents = await planAgentSetup(cwd, agentOptions(options));
|
package/dist/init/agents.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { readdir } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { readOptionalFile } from '../utils/config-discovery.js';
|
|
5
6
|
import { fileChange } from '../utils/file-changes.js';
|
|
7
|
+
import { getPackageVersion } from '../utils/package-info.js';
|
|
6
8
|
let sourcePath = fileURLToPath(new URL('../../skills/vizzly/', import.meta.url));
|
|
7
9
|
let start = '<!-- vizzly-agent-guidance -->';
|
|
8
10
|
let end = '<!-- /vizzly-agent-guidance -->';
|
|
11
|
+
export function agentGuidancePath(cwd) {
|
|
12
|
+
return path.join(cwd, '.vizzly', 'agent-guidance.json');
|
|
13
|
+
}
|
|
14
|
+
export async function agentGuidanceFlag(cwd) {
|
|
15
|
+
let agents = await readOptionalFile(path.join(cwd, 'AGENTS.md'));
|
|
16
|
+
return agents?.includes(start) ? '--agent-guidance' : '--agent-skill';
|
|
17
|
+
}
|
|
9
18
|
function guidanceContent() {
|
|
10
19
|
return `${start}
|
|
11
20
|
## Visual testing with Vizzly
|
|
@@ -35,14 +44,18 @@ function addGuidance(source) {
|
|
|
35
44
|
throw new Error('Repair the incomplete or duplicate Vizzly guidance markers in AGENTS.md before running init.');
|
|
36
45
|
}
|
|
37
46
|
let block = guidanceContent();
|
|
38
|
-
if (starts)
|
|
47
|
+
if (starts) {
|
|
48
|
+
return content.slice(0, begin) + block.trimEnd() + content.slice(finish + end.length);
|
|
49
|
+
}
|
|
39
50
|
return content.trim() ? `${content.trimEnd()}\n\n${block}` : block;
|
|
40
51
|
}
|
|
41
52
|
async function skillFiles(directory, prefix = '') {
|
|
42
53
|
let files = [];
|
|
43
|
-
|
|
54
|
+
let entries = await readdir(directory, {
|
|
44
55
|
withFileTypes: true
|
|
45
|
-
})
|
|
56
|
+
});
|
|
57
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
58
|
+
for (let entry of entries) {
|
|
46
59
|
let relative = path.join(prefix, entry.name);
|
|
47
60
|
if (entry.isDirectory()) {
|
|
48
61
|
files.push(...(await skillFiles(path.join(directory, entry.name), relative)));
|
|
@@ -52,6 +65,23 @@ async function skillFiles(directory, prefix = '') {
|
|
|
52
65
|
}
|
|
53
66
|
return files;
|
|
54
67
|
}
|
|
68
|
+
export async function bundledAgentSkill() {
|
|
69
|
+
let files = [];
|
|
70
|
+
let hash = createHash('sha256');
|
|
71
|
+
for (let filename of await skillFiles(sourcePath)) {
|
|
72
|
+
let content = await readOptionalFile(path.join(sourcePath, filename));
|
|
73
|
+
files.push({
|
|
74
|
+
filename,
|
|
75
|
+
content
|
|
76
|
+
});
|
|
77
|
+
hash.update(JSON.stringify([filename.split(path.sep).join('/'), content]));
|
|
78
|
+
}
|
|
79
|
+
hash.update(guidanceContent());
|
|
80
|
+
return {
|
|
81
|
+
files,
|
|
82
|
+
version: hash.digest('hex')
|
|
83
|
+
};
|
|
84
|
+
}
|
|
55
85
|
export async function planAgentSetup(cwd, {
|
|
56
86
|
skill,
|
|
57
87
|
guidance
|
|
@@ -61,17 +91,23 @@ export async function planAgentSetup(cwd, {
|
|
|
61
91
|
};
|
|
62
92
|
let targetPath = path.join(cwd, '.agents', 'skills', 'vizzly');
|
|
63
93
|
let existing = await readOptionalFile(path.join(targetPath, 'SKILL.md'));
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
let skillChanges = [];
|
|
95
|
+
let bundled = await bundledAgentSkill();
|
|
96
|
+
for (let {
|
|
97
|
+
filename,
|
|
98
|
+
content
|
|
99
|
+
} of bundled.files) {
|
|
100
|
+
skillChanges.push(await fileChange(path.join(targetPath, filename), content));
|
|
69
101
|
}
|
|
102
|
+
let skillStatus = 'installed';
|
|
103
|
+
if (existing) skillStatus = skillChanges.some(Boolean) ? 'updated' : 'exists';
|
|
70
104
|
let agentSkill = {
|
|
71
|
-
status:
|
|
105
|
+
status: skillStatus,
|
|
72
106
|
sourcePath,
|
|
73
|
-
targetPath
|
|
107
|
+
targetPath,
|
|
108
|
+
version: bundled.version
|
|
74
109
|
};
|
|
110
|
+
let changes = [...skillChanges];
|
|
75
111
|
let agentGuidance;
|
|
76
112
|
if (guidance) {
|
|
77
113
|
let agentsPath = path.join(cwd, 'AGENTS.md');
|
|
@@ -91,6 +127,11 @@ export async function planAgentSetup(cwd, {
|
|
|
91
127
|
agentsPath
|
|
92
128
|
};
|
|
93
129
|
}
|
|
130
|
+
let installedGuidance = {
|
|
131
|
+
version: bundled.version,
|
|
132
|
+
cliVersion: getPackageVersion()
|
|
133
|
+
};
|
|
134
|
+
changes.push(await fileChange(agentGuidancePath(cwd), `${JSON.stringify(installedGuidance, null, 2)}\n`));
|
|
94
135
|
return {
|
|
95
136
|
changes: changes.filter(Boolean),
|
|
96
137
|
agentSkill,
|