naro-cli 0.13.0 → 0.15.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
@@ -69,9 +69,13 @@ naro logs <deployment-id> Build logs of a deployment
69
69
  naro logs --type build --follow Stream build logs until READY/FAILED
70
70
  naro logs --env preview Preview-surface runtime logs
71
71
 
72
- naro insights Speed Insights: Web Vitals p75 (--metric lcp, --range 7d)
72
+ naro analytics Web Analytics: visitors, page views, top pages, referrers, countries, devices, browsers, operating systems (--range 7d)
73
+ naro analytics --country KR --os ios Narrow it: --path, --referrer, --country, --device, --browser, --os (one value each, all must match)
74
+
75
+ naro insights Speed Insights: Web Vitals p75, and the page load's p75 and the mean of each step (--metric lcp, --range 7d)
73
76
  naro insights --route '/posts/[id]' The paths under one route
74
77
  naro insights --env preview The same for the preview worker
78
+ naro insights --country KR --device mobile Narrow it: --path, --country, --device, --browser, --os (no --referrer: Speed Insights has none)
75
79
  naro insights enable | disable Turn measuring on or off
76
80
 
77
81
  naro protection Preview protection: on or off, and whether a bypass secret is set
@@ -169,7 +173,7 @@ naro policy set posts --from-file policy.json
169
173
  Shorthand: writes the policies naro_select … naro_delete
170
174
  naro policy remove todos Drop every policy on the table: back to deny (asks first)
171
175
 
172
- naro storage Naro Storage: on or off, limits, usage, buckets
176
+ naro storage Naro Storage: on or off, limits, usage, today's operations, buckets
173
177
  naro storage enable Turn it on: the project's R2 bucket, bound to the backend
174
178
  naro storage bucket create avatars --public --file-size-limit 5MiB --allowed-mime-types image/png,image/jpeg
175
179
  naro storage bucket update avatars --private | --file-size-limit none | --allowed-mime-types any
@@ -789,6 +793,53 @@ An `--update-check` you give replaces the default: without
789
793
 
790
794
  ## Release notes
791
795
 
796
+ ### 0.15.0
797
+
798
+ - `naro storage` shows the Storage meter's day between `Used:` and `Buckets:`:
799
+ `Today (UTC):` has the write operations (R2 Class A) against the daily limit
800
+ and the reads, `Measured:` the bucket size R2 reported and when R2 measured
801
+ it (R2 reports the size 30–45 minutes late, and the meter reads it every 15
802
+ minutes). A `✗` line
803
+ says uploads are stopped, over the storage limit until objects are deleted
804
+ or over the day's write operations until 00:00 UTC (downloads still work);
805
+ a `⚠` line warns from 80% of the storage limit or of the day's write
806
+ operations. Before the meter has run for the UTC day nothing is added, and
807
+ `--json` prints `storage.today` and `storage.limits.dailyOps` as the
808
+ control plane answered.
809
+
810
+ ### 0.14.0
811
+
812
+ - `naro analytics` shows the project's Web Analytics from the terminal: the
813
+ range's visitors and page views (`--range 24h|7d|30d`, 7d by default), then
814
+ the top pages, referrers, countries, devices, browsers and operating systems
815
+ by page views, and `--json` prints the whole report, trend included. The
816
+ same six filters as on the dashboard narrow it, one value each, all of them
817
+ at once: `naro analytics --country KR --os ios`. The first line names the
818
+ filters that applied; filters that leave no visit print one line under it,
819
+ `No visits match these filters. Drop a filter to see more.`, instead of six
820
+ empty tables. The CLI does not check the values; the server answers
821
+ `400 VALIDATION_FAILED` with the rule the value broke (`--country` is an
822
+ uppercase two-letter code like `KR`, `--referrer` a lowercase host like
823
+ `t.co`; `kr` and `T.co` are refused). It reads the
824
+ production worker only, so `--env` is refused with `VALIDATION_FAILED` and
825
+ nothing is sent. An API token (`--token`, `NARO_TOKEN`) can read it.
826
+ - `naro insights` takes the same filters: `--path`, `--country`, `--device`,
827
+ `--browser` and `--os` narrow it to one value each (`--referrer` is refused
828
+ with `VALIDATION_FAILED` and nothing is sent: a vitals row has no referrer).
829
+ The first line names the filters that applied
830
+ (`· filtered by country=KR, os=ios`); filters that leave no measurement print
831
+ `No measurements match these filters. Drop a filter to see more.` instead of
832
+ waiting advice.
833
+ The filters narrow a report only: `naro insights enable | disable` refuses
834
+ them (`Filters only narrow a report; drop --country.`) and sends nothing, and
835
+ an empty value (`--referrer=`) is no filter.
836
+ - `naro insights` prints a `Page load p75 … · mean per step: …` line under the
837
+ metrics table: the p75 of how long visits took to load the page, to the end
838
+ of the load event, then the mean of each step (wait, DNS, connect, server,
839
+ download, processing, onload), so the steps need not add up to the p75. It is
840
+ left out until a visit has sent a page load. The dashboard's Speed Insights
841
+ tab has the same Page load card.
842
+
792
843
  ### 0.13.0
793
844
 
794
845
  - `naro insights` shows the project's Speed Insights from the terminal: each
@@ -294,9 +294,6 @@ function pathnameOf(url2) {
294
294
  }
295
295
  }
296
296
 
297
- // ../../packages/core/src/internal-auth.ts
298
- import { Buffer as Buffer2 } from "buffer";
299
-
300
297
  // ../../packages/core/src/migration-names.ts
301
298
  function normalizeMigrationName(name) {
302
299
  return name.trim().toLowerCase().replace(/\.sql$/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
@@ -15253,6 +15250,20 @@ function classifyNextVersion(raw, now) {
15253
15250
  // ../../packages/build/src/package-manager.ts
15254
15251
  import { access, readFile as readFile2 } from "fs/promises";
15255
15252
  import { join as join2 } from "path";
15253
+ function cacheEnv(dir) {
15254
+ return {
15255
+ npm_config_store_dir: dir,
15256
+ npm_config_cache: dir,
15257
+ YARN_CACHE_FOLDER: dir
15258
+ };
15259
+ }
15260
+ function withSharedCache(spec, dir) {
15261
+ return {
15262
+ ...spec,
15263
+ addArgs: (specs) => [...spec.addArgs(specs), ...spec.cacheArgs(dir)],
15264
+ addEnv: { ...spec.addEnv, ...cacheEnv(dir) }
15265
+ };
15266
+ }
15256
15267
  async function exists(path) {
15257
15268
  try {
15258
15269
  await access(path);
@@ -15706,7 +15717,7 @@ export default defineCloudflareConfig({});
15706
15717
 
15707
15718
  // ../../packages/build/src/vinext.ts
15708
15719
  var VINEXT_TOOLCHAIN = [
15709
- "vinext@1.0.0-beta.9",
15720
+ "vinext@1.1.0",
15710
15721
  "@cloudflare/vite-plugin@1.54.7",
15711
15722
  "@vitejs/plugin-react@6.1.1",
15712
15723
  "@vitejs/plugin-rsc@0.5.34",
@@ -15722,7 +15733,7 @@ var VITE_CONFIG_NAMES = [
15722
15733
  "vite.config.mts",
15723
15734
  "vite.config.cts"
15724
15735
  ];
15725
- var GENERATED_VITE_CONFIG = "vite.config.mjs";
15736
+ var GENERATED_VITE_CONFIG = "vite.config.js";
15726
15737
  var OUTPUT_DIR = "dist";
15727
15738
  var SERVER_DIR = "server";
15728
15739
  var CLIENT_DIR = "client";
@@ -15787,6 +15798,8 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
15787
15798
  }
15788
15799
  var AUTO_FIXED_ISSUES = [/Missing "type": "module"/i];
15789
15800
  var ISSUE_LINE = /^\s*✗\s+(.+)$/;
15801
+ var PARTIAL_LINE = /^\s*~\s+(.+)$/;
15802
+ var BLOCKING_PARTIALS = [/^webpack\b/i];
15790
15803
  var ANSI_ESCAPE = /\u001B\[[0-9;]*m/g;
15791
15804
  var SUMMARY_LINE = /Overall:\s*(\d+)%\s*compatible\s*\(\s*(\d+)\s+supported,\s*(\d+)\s+partial,\s*(\d+)\s+issues?\s*\)/i;
15792
15805
  function parseVinextCheckReport(output) {
@@ -15798,12 +15811,18 @@ function parseVinextCheckReport(output) {
15798
15811
  plain.split("\n").map((line) => ISSUE_LINE.exec(line)?.[1]?.trim()).filter((line) => Boolean(line))
15799
15812
  )
15800
15813
  ];
15814
+ const partialLines = [
15815
+ ...new Set(
15816
+ plain.split("\n").map((line) => PARTIAL_LINE.exec(line)?.[1]?.trim()).filter((line) => Boolean(line))
15817
+ )
15818
+ ];
15801
15819
  return {
15802
15820
  percent: Number(match[1]),
15803
15821
  supported: Number(match[2]),
15804
15822
  partial: Number(match[3]),
15805
15823
  issues: Number(match[4]),
15806
- issueLines
15824
+ issueLines,
15825
+ partialLines
15807
15826
  };
15808
15827
  }
15809
15828
  function vinextRouteFromCheck(report) {
@@ -15815,7 +15834,10 @@ function vinextRouteFromCheck(report) {
15815
15834
  const autoFixed = report.issueLines.filter(
15816
15835
  (line) => AUTO_FIXED_ISSUES.some((pattern) => pattern.test(line))
15817
15836
  );
15818
- const blocking = Math.max(0, report.issues - autoFixed.length);
15837
+ const blockingPartials = report.partialLines.filter(
15838
+ (line) => BLOCKING_PARTIALS.some((pattern) => pattern.test(line))
15839
+ );
15840
+ const blocking = Math.max(0, report.issues - autoFixed.length) + blockingPartials.length;
15819
15841
  if (blocking > 0)
15820
15842
  return {
15821
15843
  eligible: false,
@@ -17836,6 +17858,7 @@ function createVinextCloudflareStrategy(options) {
17836
17858
  // Not production: npm and classic Yarn read that as omit=dev and
17837
17859
  // would prune the project's devDependencies while adding ours.
17838
17860
  env: {
17861
+ ...context.packageManager.addEnv,
17839
17862
  NODE_ENV: "development",
17840
17863
  NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath
17841
17864
  },
@@ -18217,11 +18240,7 @@ var LocalBuildExecutor = class {
18217
18240
  env: {
18218
18241
  NODE_ENV: "development",
18219
18242
  NPM_CONFIG_USERCONFIG: packageManagerConfigPath,
18220
- ...cachePorts ? {
18221
- npm_config_store_dir: pmCacheDir,
18222
- npm_config_cache: pmCacheDir,
18223
- YARN_CACHE_FOLDER: pmCacheDir
18224
- } : {}
18243
+ ...cachePorts ? cacheEnv(pmCacheDir) : {}
18225
18244
  },
18226
18245
  timeoutMs: 10 * 6e4,
18227
18246
  stage: "INSTALLING",
@@ -18305,7 +18324,9 @@ var LocalBuildExecutor = class {
18305
18324
  identity,
18306
18325
  branch: input.branch,
18307
18326
  env: input.env ?? {},
18308
- packageManager,
18327
+ // The toolchain add has to use the store the install linked
18328
+ // node_modules from (withSharedCache).
18329
+ packageManager: cachePorts ? withSharedCache(packageManager, pmCacheDir) : packageManager,
18309
18330
  packageManagerConfigPath,
18310
18331
  buildScript: resolved.buildScript,
18311
18332
  nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown",
@@ -18488,6 +18509,16 @@ function createLogSender(ports) {
18488
18509
  };
18489
18510
  }
18490
18511
 
18512
+ // ../../packages/sdk/src/analytics.ts
18513
+ var ANALYTICS_FILTER_KEYS = [
18514
+ "path",
18515
+ "referrer",
18516
+ "country",
18517
+ "device",
18518
+ "browser",
18519
+ "os"
18520
+ ];
18521
+
18491
18522
  // ../../packages/sdk/src/auth.ts
18492
18523
  import { homedir as homedir2 } from "os";
18493
18524
 
@@ -18960,6 +18991,23 @@ function openBrowser(url2) {
18960
18991
  child.unref();
18961
18992
  }
18962
18993
 
18994
+ // ../../packages/sdk/src/insights.ts
18995
+ var SPEED_INSIGHTS_METRICS = [
18996
+ "lcp",
18997
+ "inp",
18998
+ "cls",
18999
+ "fcp",
19000
+ "ttfb"
19001
+ ];
19002
+ var SPEED_INSIGHTS_FILTER_KEYS = [
19003
+ "path",
19004
+ "country",
19005
+ "device",
19006
+ "browser",
19007
+ "os"
19008
+ ];
19009
+ var SPEED_INSIGHTS_MIN_DEPLOYMENT_SAMPLES = 20;
19010
+
18963
19011
  // ../../packages/sdk/src/types.ts
18964
19012
  var TERMINAL_DEPLOYMENT_STATUSES = [
18965
19013
  "READY",
@@ -19052,6 +19100,7 @@ var NaroClient = class {
19052
19100
  backend;
19053
19101
  storage;
19054
19102
  insights;
19103
+ analytics;
19055
19104
  protection;
19056
19105
  constructor(options) {
19057
19106
  this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
@@ -19069,6 +19118,7 @@ var NaroClient = class {
19069
19118
  this.backend = new BackendApi(this);
19070
19119
  this.storage = new StorageApi(this);
19071
19120
  this.insights = new InsightsApi(this);
19121
+ this.analytics = new AnalyticsApi(this);
19072
19122
  this.protection = new ProtectionApi(this);
19073
19123
  }
19074
19124
  async request(path, options) {
@@ -19477,12 +19527,20 @@ var LogsApi = class {
19477
19527
  return body.entries;
19478
19528
  }
19479
19529
  };
19530
+ function appendFilters(params, keys, filters) {
19531
+ if (!filters) return;
19532
+ for (const key of keys) {
19533
+ const value = filters[key];
19534
+ if (value) params.set(key, value);
19535
+ }
19536
+ }
19480
19537
  function insightsQuery(options) {
19481
19538
  const params = new URLSearchParams();
19482
19539
  if (options.range !== void 0) params.set("range", options.range);
19483
19540
  if (options.metric !== void 0) params.set("metric", options.metric);
19484
19541
  if (options.environment !== void 0) params.set("env", options.environment);
19485
19542
  if (options.route !== void 0) params.set("route", options.route);
19543
+ appendFilters(params, SPEED_INSIGHTS_FILTER_KEYS, options.filters);
19486
19544
  const text2 = params.toString();
19487
19545
  return text2 ? `?${text2}` : "";
19488
19546
  }
@@ -19493,7 +19551,7 @@ var InsightsApi = class {
19493
19551
  base(projectId) {
19494
19552
  return `/api/projects/${encodeURIComponent(projectId)}/speed-insights`;
19495
19553
  }
19496
- /** The five metrics' summary, and the trend, pages, devices and elements of one. */
19554
+ /** The five metrics' summary, and the trend, pages, devices, browsers, operating systems, countries and elements of one. */
19497
19555
  async report(projectId, options = {}) {
19498
19556
  return this.client.request(
19499
19557
  `${this.base(projectId)}${insightsQuery(options)}`,
@@ -19553,6 +19611,22 @@ var InsightsApi = class {
19553
19611
  });
19554
19612
  }
19555
19613
  };
19614
+ var AnalyticsApi = class {
19615
+ constructor(client) {
19616
+ this.client = client;
19617
+ }
19618
+ /** Visitors, page views, the trend, and the pages, referrers, countries, devices, browsers and operating systems behind them. */
19619
+ async report(projectId, options = {}) {
19620
+ const params = new URLSearchParams();
19621
+ if (options.range !== void 0) params.set("range", options.range);
19622
+ appendFilters(params, ANALYTICS_FILTER_KEYS, options.filters);
19623
+ const text2 = params.toString();
19624
+ return this.client.request(
19625
+ `/api/projects/${encodeURIComponent(projectId)}/analytics${text2 ? `?${text2}` : ""}`,
19626
+ { context: "Read Analytics" }
19627
+ );
19628
+ }
19629
+ };
19556
19630
  var ProtectionApi = class {
19557
19631
  constructor(client) {
19558
19632
  this.client = client;
@@ -21056,6 +21130,12 @@ var CATALOG = {
21056
21130
  status: 507,
21057
21131
  message: () => "This project has used all of its storage. Delete objects to free space; uploads fail until then."
21058
21132
  },
21133
+ // Storage 3-1d (design §6.4): like WRITE_QUOTA_EXCEEDED, a 429 that clears
21134
+ // by itself at midnight; downloads, deletes and listings are not refused.
21135
+ STORAGE_OPS_QUOTA_EXCEEDED: {
21136
+ status: 429,
21137
+ message: () => "This project has reached its daily storage operation limit. Downloads still work; uploads resume at 00:00 UTC."
21138
+ },
21059
21139
  // R2 or its binding did not answer; the original error is logged, never sent.
21060
21140
  STORAGE_UNAVAILABLE: {
21061
21141
  status: 503,
@@ -27333,16 +27413,6 @@ function generateTypes(tables, options = {}) {
27333
27413
  `;
27334
27414
  }
27335
27415
 
27336
- // ../../packages/sdk/src/insights.ts
27337
- var SPEED_INSIGHTS_METRICS = [
27338
- "lcp",
27339
- "inp",
27340
- "cls",
27341
- "fcp",
27342
- "ttfb"
27343
- ];
27344
- var SPEED_INSIGHTS_MIN_DEPLOYMENT_SAMPLES = 20;
27345
-
27346
27416
  // ../../packages/sdk/src/link.ts
27347
27417
  import {
27348
27418
  existsSync as existsSync4,
@@ -28464,6 +28534,7 @@ export {
28464
28534
  schemaMigrationTitle,
28465
28535
  projectPath,
28466
28536
  splitStatements,
28537
+ ANALYTICS_FILTER_KEYS,
28467
28538
  NaroError,
28468
28539
  isNaroError,
28469
28540
  toNaroError,
@@ -28477,6 +28548,9 @@ export {
28477
28548
  login,
28478
28549
  resolveAuth,
28479
28550
  openBrowser,
28551
+ SPEED_INSIGHTS_METRICS,
28552
+ SPEED_INSIGHTS_FILTER_KEYS,
28553
+ SPEED_INSIGHTS_MIN_DEPLOYMENT_SAMPLES,
28480
28554
  isTerminalStatus,
28481
28555
  BACKEND_STEPS,
28482
28556
  isStalledBackend,
@@ -28515,8 +28589,6 @@ export {
28515
28589
  formatRedirectEntry,
28516
28590
  policiesForTypes,
28517
28591
  generateTypes,
28518
- SPEED_INSIGHTS_METRICS,
28519
- SPEED_INSIGHTS_MIN_DEPLOYMENT_SAMPLES,
28520
28592
  readProjectLink,
28521
28593
  writeProjectLink,
28522
28594
  removeProjectLink,
package/dist/deploy.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  parseDotenv,
6
6
  readProjectEnv,
7
7
  slugify
8
- } from "./chunk-O4NJRDJE.js";
8
+ } from "./chunk-6WJ3QQ5Y.js";
9
9
  import "./chunk-K3NQKI34.js";
10
10
  export {
11
11
  deployDirectory,