svelte-vitals 0.22.0 → 0.23.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 CHANGED
@@ -34,14 +34,12 @@ Critical (1)
34
34
  src/routes/none/+page.svelte
35
35
 
36
36
  Passed (3)
37
- ────────────────────────
38
- ✓ SEO001 <title> /blog
39
- ✓ SEO001 <title> ↯ dynamic /dynamic
40
- ✓ SEO001 <title> /static
41
37
 
42
38
  ↯ = set dynamically (verified at runtime).
43
39
  ```
44
40
 
41
+ By default, console output groups failures by rule (top 5 per severity, each with one example location and an "…and N more" count) and collapses the Passed section to a bare count, so large projects don't flood the terminal. Pass `--verbose` to see every finding uncapped and ungrouped, with each passed item listed individually. On an interactive, color-capable terminal the Health score plays a short reveal animation; pass `--no-animation` to disable it.
42
+
45
43
  ### Exit codes
46
44
 
47
45
  | Code | Meaning |
package/dist/bin.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  readCoreVersion,
8
8
  readPackageVersion,
9
9
  run
10
- } from "./chunk-KSJZLF5U.js";
10
+ } from "./chunk-HYTPZVSU.js";
11
11
 
12
12
  // src/bin.ts
13
13
  import mri3 from "mri";
@@ -134,6 +134,7 @@ function resolveArgs(argv) {
134
134
  if (score && (argv.json || typeof argv.reporter === "string")) {
135
135
  warnings.push("svelte-vitals: --score overrides --reporter; reporter output suppressed.");
136
136
  }
137
+ const verbose = Boolean(argv["verbose"]);
137
138
  const rulesConfig = buildRulesConfig(allow, ignore);
138
139
  const rules = Object.keys(rulesConfig).length > 0 ? rulesConfig : void 0;
139
140
  if (errors.length > 0) return { options: null, warnings, errors };
@@ -154,6 +155,7 @@ function resolveArgs(argv) {
154
155
  ...weights !== void 0 ? { weights } : {},
155
156
  ...categories !== void 0 ? { categories } : {},
156
157
  ...score ? { score } : {},
158
+ ...verbose ? { verbose } : {},
157
159
  ...diffBase !== void 0 ? { diffBase } : {},
158
160
  ...staged ? { staged } : {},
159
161
  ...baselineRef !== void 0 ? { baseline: baselineRef } : {}
@@ -843,6 +845,8 @@ import mri2 from "mri";
843
845
 
844
846
  // src/ci/workflow.ts
845
847
  var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
848
+ var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
849
+ var CHECKOUT_VERSION = "v7.0.0";
846
850
  function planWorkflowWrite(existing, force) {
847
851
  if (existing === void 0) return { status: "created" };
848
852
  if (!force) return { status: "exists" };
@@ -866,7 +870,7 @@ function buildWorkflowYaml(opts) {
866
870
  " svelte-vitals:",
867
871
  " runs-on: ubuntu-latest",
868
872
  " steps:",
869
- " - uses: actions/checkout@v4",
873
+ ` - uses: actions/checkout@${CHECKOUT_SHA} # ${CHECKOUT_VERSION}`,
870
874
  " with:",
871
875
  " fetch-depth: 0",
872
876
  ` - uses: oekazuma/svelte-vitals/packages/action@${actionSha} # @svelte-vitals/action@${actionVersion}`,
@@ -878,8 +882,8 @@ function buildWorkflowYaml(opts) {
878
882
  }
879
883
 
880
884
  // src/ci/action-pin.generated.ts
881
- var ACTION_SHA = "86e84073bddea99530d2d6831378a68836443ee4";
882
- var ACTION_VERSION = "0.2.0";
885
+ var ACTION_SHA = "caf278cf4030801b017d31b108fa46abc15e8105";
886
+ var ACTION_VERSION = "0.2.2";
883
887
 
884
888
  // src/ci/cli.ts
885
889
  var CI_HELP = `svelte-vitals ci \u2014 scaffold CI integration
@@ -967,6 +971,8 @@ Options:
967
971
  --weights <pairs> Per-category Health weight overrides, e.g. seo=2,performance=1 (unlisted categories default to 1)
968
972
  --score Print only the combined Health score (works with --min-health for gating)
969
973
  --no-color Disable ANSI color in console output
974
+ --no-animation Disable the Health-score reveal animation on an interactive terminal
975
+ --verbose Show every finding uncapped and ungrouped (default: capped, grouped by rule)
970
976
  -h, --help Show this help
971
977
  -v, --version Show version
972
978
 
@@ -997,7 +1003,7 @@ async function main() {
997
1003
  }
998
1004
  const argv = mri3(process.argv.slice(2), {
999
1005
  alias: { h: "help", v: "version" },
1000
- boolean: ["by-route", "json", "fail-on-warning", "staged", "no-color", "score"],
1006
+ boolean: ["by-route", "json", "fail-on-warning", "staged", "no-color", "score", "verbose", "no-animation"],
1001
1007
  string: [
1002
1008
  "meta-components",
1003
1009
  "treat-dynamic-as",
@@ -1036,7 +1042,13 @@ async function main() {
1036
1042
  }
1037
1043
  minHealth = n;
1038
1044
  }
1039
- const code = await run({ ...options, minHealth, noColor: argv["no-color"], selectApp });
1045
+ const code = await run({
1046
+ ...options,
1047
+ minHealth,
1048
+ noColor: argv["no-color"],
1049
+ noAnimation: argv["no-animation"],
1050
+ selectApp
1051
+ });
1040
1052
  process.exit(code);
1041
1053
  }
1042
1054
  void main();
@@ -702,6 +702,9 @@ function isAgentEnv(env = process.env) {
702
702
  function isGithubActionsEnv(env = process.env) {
703
703
  return env.GITHUB_ACTIONS === "true";
704
704
  }
705
+ function isCiEnv(env = process.env) {
706
+ return env.CI === "true" || env.CI === "1";
707
+ }
705
708
  function resolveReporter(explicit, env = process.env) {
706
709
  if (explicit) return explicit;
707
710
  const fromEnv = env.SVELTE_VITALS_REPORTER;
@@ -953,6 +956,40 @@ async function loadConfigFile(cwd) {
953
956
  return validateConfigFile(mod.default, found);
954
957
  }
955
958
 
959
+ // src/pulse-animation.ts
960
+ import { scoreColor } from "@svelte-vitals/core";
961
+ var FRAME_COUNT = 6;
962
+ var FRAME_DELAY_MS = 200;
963
+ var WAVE_FRAMES = [
964
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2571\u2572\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
965
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2571\u2572\u2500\u2500\u2571\u2572\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
966
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2571\u2572\u2500\u2500\u2500\u2500\u2500\u2500\u2571\u2572\u2500\u2500\u2500\u2500\u2500\u2500",
967
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2571\u2572\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2572\u2500\u2500\u2500\u2500\u2500",
968
+ "\u2500\u2500\u2500\u2500\u2571\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2572\u2500\u2500\u2500\u2500",
969
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
970
+ ];
971
+ function sleep(ms) {
972
+ return new Promise((resolve) => setTimeout(resolve, ms));
973
+ }
974
+ async function playScoreAnimation(opts) {
975
+ const frameDelayMs = opts.frameDelayMs ?? FRAME_DELAY_MS;
976
+ for (let frame = 0; frame < FRAME_COUNT; frame++) {
977
+ const progress = frame / (FRAME_COUNT - 1);
978
+ const displayScore = Math.round(opts.score * progress);
979
+ const isFinalFrame = frame === FRAME_COUNT - 1;
980
+ const wave = WAVE_FRAMES[frame];
981
+ const scoreText = isFinalFrame ? scoreColor(opts.palette, opts.score)(`${displayScore}/100`) : opts.palette.dim(`${displayScore}/100`);
982
+ const cursorUp = frame === 0 ? "" : "\x1B[2A";
983
+ opts.stream.write(`${cursorUp}\r ${wave}\x1B[K
984
+ \r Health: ${scoreText}\x1B[K
985
+ `);
986
+ if (!isFinalFrame) await sleep(frameDelayMs);
987
+ }
988
+ }
989
+ function scoreAnimationEnabled(opts) {
990
+ return opts.reporter === "console" && opts.stdoutIsTTY && !opts.noAnimationFlag && !isAgentEnv(opts.env) && !isCiEnv(opts.env) && colorEnabled({ reporter: opts.reporter, isTTY: opts.stdoutIsTTY, env: opts.env, noColorFlag: opts.noColorFlag });
991
+ }
992
+
956
993
  // src/index.ts
957
994
  import { defineConfig as defineConfig2 } from "@svelte-vitals/core";
958
995
  function spinnerEnabled(opts) {
@@ -1172,13 +1209,37 @@ async function run(opts = {}) {
1172
1209
  } else if (reporter === "md") {
1173
1210
  log(formatMarkdownReport(results, config, { version }));
1174
1211
  } else {
1212
+ const stdoutIsTTY = opts.stdoutIsTTY ?? !!process.stdout.isTTY;
1175
1213
  const colorOn = colorEnabled({
1176
1214
  reporter,
1177
- isTTY: opts.stdoutIsTTY ?? !!process.stdout.isTTY,
1215
+ isTTY: stdoutIsTTY,
1178
1216
  env,
1179
1217
  noColorFlag: opts.noColor
1180
1218
  });
1181
- log(formatConsoleReport(results, config, { byRoute: opts.byRoute ?? false, palette: paletteFor(colorOn) }));
1219
+ const palette = paletteFor(colorOn);
1220
+ const animate = scoreAnimationEnabled({
1221
+ reporter,
1222
+ stdoutIsTTY,
1223
+ env,
1224
+ noColorFlag: opts.noColor,
1225
+ noAnimationFlag: opts.noAnimation
1226
+ });
1227
+ if (animate) {
1228
+ await playScoreAnimation({
1229
+ score: computeHealth(results, config).health,
1230
+ palette,
1231
+ stream: opts.stdoutStream ?? process.stdout,
1232
+ frameDelayMs: opts.animationFrameDelayMs
1233
+ });
1234
+ }
1235
+ log(
1236
+ formatConsoleReport(results, config, {
1237
+ byRoute: opts.byRoute ?? false,
1238
+ verbose: opts.verbose ?? false,
1239
+ palette,
1240
+ omitHeader: animate
1241
+ })
1242
+ );
1182
1243
  }
1183
1244
  }
1184
1245
  const summary = summarize(results, config);
package/dist/index.d.ts CHANGED
@@ -89,6 +89,14 @@ interface RunOptions {
89
89
  explicitPath?: boolean;
90
90
  /** Injected picker for the monorepo app selector (bin.ts wires a clack implementation; null = cancelled). */
91
91
  selectApp?: (apps: string[]) => Promise<string | null>;
92
+ /** Show every finding uncapped and ungrouped in console output (default false — capped, grouped by rule). */
93
+ verbose?: boolean;
94
+ /** Disable the Health-score reveal animation even on an interactive stdout. */
95
+ noAnimation?: boolean;
96
+ /** Override the stream the score animation writes to (tests). Defaults to process.stdout. */
97
+ stdoutStream?: NodeJS.WriteStream;
98
+ /** Override the animation's per-frame delay in ms (tests — 0 runs the real frame loop near-instantly). Defaults to the animation module's own constant. */
99
+ animationFrameDelayMs?: number;
92
100
  }
93
101
  /**
94
102
  * Whether the "Analyzing…" spinner should run. Unlike color, the spinner animates
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  routeMatcher,
11
11
  run,
12
12
  spinnerEnabled
13
- } from "./chunk-KSJZLF5U.js";
13
+ } from "./chunk-HYTPZVSU.js";
14
14
  export {
15
15
  ProjectError,
16
16
  analyzeProject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-vitals",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "A SvelteKit SEO checker — not a runtime Web Vitals reporter. Static analysis of your routes' head metadata.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "smol-toml": "^1.7.0",
46
46
  "svelte": "^5.56.4",
47
47
  "tinyglobby": "^0.2.17",
48
- "@svelte-vitals/core": "0.22.0"
48
+ "@svelte-vitals/core": "0.23.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^24.13.2"