@astroscope/node

中文文档
5.0.1发布于 2026/11/03MITTypeScriptES Module构建来源已验证近 30 天下载 1968

Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probes, request logging, telemetry, CSRF and static serving run as plain code around server.listen()

原始使用文档
文档目录23 节
  1. What it does
  2. What it does NOT do — beware
  3. Usage
  4. Boot context access
  5. Lifecycle events
  6. Platform entry files
  7. Logging
  8. Reporting the route yourself
  9. Telemetry
  10. Health checks
  11. Native mounts
  12. Guards
  13. Available guards
  14. Exclude patterns
  15. Island preloading
  16. Pre-compressed static serving
  17. Embedded build tweaks
  18. Image processing
  19. Options
  20. HTTPS
  21. Environment variables
  22. Shutdown sequence
  23. License

@astroscope/node

Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probes, request logging, telemetry, CSRF and static serving run as plain code around server.listen().

What it does

  • Boot lifecycle — onStartup runs before the port opens, onShutdown after in-flight requests drain (src/boot.ts)
  • Warmup — pages, middleware and actions are loaded in parallel with startup and awaited before listen()
  • Health probes — Kubernetes-style liveness/readiness on a separate port, with registrable checks
  • Request logging — pino at the native handler: real status code, response size, ttfb, aborted connections, static responses included
  • Telemetry — OpenTelemetry NodeSDK, undici fetch instrumentation, runtime + host metrics, Prometheus reader
  • CSRF protection — origin check for unsafe methods, with path exclusions
  • Path redirects — duplicate slashes (//about) and a trailing slash that contradicts trailingSlash (/about/) redirect to the canonical URL, in dev as in prod (astro's dev server answers the latter with a 404 page instead)
  • Platform entry files — env loading → src/config.ts → src/instrumentation.ts → src/log.ts → boot, each picked up automatically when the file exists
  • Island preloading — each island's JS is preloaded in parallel instead of being discovered module by module, removing the hydration request waterfall (Island preloading)
  • Pre-compressed static serving — build-time brotli/gzip variants, negotiated per Accept-Encoding
  • Native mounts — http-native handlers (oidc-provider, ACME) mounted on the adapter's server
  • Build tweaks — SSR sourcemaps
  • Image processing off unless configured — without an explicit image.service, any astro:assets use fails loudly instead of opening astro's on-demand sharp endpoint (Image processing)
  • Dev restart machinery — changes to the boot file or entry seams restart the dev server behind a holding page
  • Dev island warmup — .astro sources are scanned for client:* components; their deps are pre-optimized and their module graphs warmed at server start, preventing "504 Outdated Optimize Dep" hydration failures from vite's lazy dep discovery
  • HTTPS for development — SERVER_CERT_PATH / SERVER_KEY_PATH serve TLS directly for local runs of the built server (HTTPS); in production, terminate TLS at the ingress

What it does NOT do — beware

The adapter assumes a container behind a load balancer / reverse proxy (Kubernetes, Docker + ingress). Outside that setup, several defaults are wrong for you:

  • Opens 0.0.0.0:9090 in production — the health probe server. Meant for the kubelet; do not expose it publicly.
  • Opens 0.0.0.0:9464 in production — the Prometheus metrics reader. Same: cluster-internal only.
  • Trusts any Host / X-Forwarded-Host — sets security.allowedDomains: [{}] unless you set it yourself, because the reverse proxy is expected to control these headers. Without one, host header injection is possible.
  • Trusts X-Forwarded-For — anything that keys on the client address uses the header as received, because the reverse proxy is expected to set it. Exposed directly, a client can send its own.
  • Overrides Astro security and config defaults — security.checkOrigin: false (the embedded CSRF middleware replaces it; csrf: false restores Astro's check), build.redirects: false (redirects handled at runtime), trailingSlash: 'never' (only when yours is at the default 'ignore'), astro:assets disabled when image.service is at its default (Image processing).
  • Limits request bodies to 1 MiB — every request astro handles, pages and endpoints included, not only actions as astro does. An upload route needs bodySizeLimit raised (native mounts are not covered).
  • Standalone only. No middleware mode — the adapter always owns the server.
  • No session driver. Astro sessions are unsupported unless you configure session.driver yourself.
  • No health probes in dev. The health server only exists in production and astro preview.

Usage

// astro.config.ts
import node from '@astroscope/node';
import { defineConfig } from 'astro/config';

export default defineConfig({
  output: 'server',
  adapter: node(),
});
// src/boot.ts — picked up automatically (or src/boot/index.ts)
import type { BootContext } from '@astroscope/node';

export async function onStartup(context: BootContext) {
  // connect to databases, warm caches, schedule timers
}

export async function onShutdown() {
  // close connections, clear timers
}

astro preview runs the full production path (boot, warmup, health, static serving).

In dev, the boot file and the entry files below run through the adapter's own watch/restart machinery: changes to the boot file, its dependency graph, or the config/log files restart the dev server behind a holding page that releases when the new generation is ready.

Boot context access

Anywhere in server code (including libraries), getBootContext() returns the running server's BootContext — or undefined when no @astroscope/node server has booted in the process (unit tests, one-off scripts). Libraries can use it to adapt to dev mode without making consumers thread the flag through:

import { getBootContext } from '@astroscope/node/boot';

const dev = getBootContext()?.dev ?? false;

It is stamped before the platform entry files and the boot lifecycle run, in both prod and dev (re-stamped per dev restart generation).

Lifecycle events

A library that needs to run at boot or shutdown without a line in the site's src/boot.ts listens on @astroscope/node/events: on(event, handler) and off(event, handler) for beforeOnStartup, afterOnStartup, beforeOnShutdown and afterOnShutdown. Handlers get the BootContext, run in registration order and are awaited one by one; afterOnShutdown runs even when onShutdown threw.

import { on } from '@astroscope/node/events';

on('beforeOnShutdown', async () => {
  await flushPendingWork();
});

Platform entry files

Besides src/boot.ts, three more files are picked up automatically when they exist. In production they run in this order inside startServer(), before anything else; in dev they re-run per restart generation (instrumentation only once per process):

env loading → src/config.ts → src/instrumentation.ts → src/log.ts → boot → warmup → listen

Env loading: CONFIG_PATH env var → ./.env → none. Existing process env vars win; each outcome is logged.

src/config.ts — schema + load (e.g. @entwico/zod-conf), nothing else; validation runs at import. On failure: in production the buffered logs are dumped to the console with the error and the process exits 1; in dev the boot gate keeps the holding page up with the error.

// src/config.ts
import zc from '@entwico/zod-conf';

export const config = zc.define({ MONGO_URL: zc.env('MONGO_URL').string() }).load({ env: process.env });

src/instrumentation.ts — extra instrumentation only (the standard bundle is a platform default, see Telemetry). Loaded once per process — dev restarts are in-process, so changes here need a full dev-server restart.

// src/instrumentation.ts
import type { InstrumentationContext } from '@astroscope/node';

export function register(ctx: InstrumentationContext) {
  // register additional instrumentations, processors, exporters
}

src/log.ts — pino logger options (a static object or a factory), never a logger instance. The platform constructs the logger itself, after instrumentation, and adds a mixin that stamps trace_id/span_id/trace_flags onto every entry when a span is active. This file runs after env loading and src/config.ts, so the options may safely read config.

// src/log.ts
import type { LoggerOptions } from '@astroscope/node/log';

export default {
  base: { app: 'my-app' },
} satisfies LoggerOptions;

Use the factory form when the options depend on the runtime context:

// src/log.ts
import type { LoggerOptionsFactory } from '@astroscope/node/log';

const factory: LoggerOptionsFactory = ({ dev }) => ({
  level: dev ? 'debug' : 'info',
});

export default factory;

Logging

The log proxy is request-aware: inside a request its entries carry the request bindings (reqId, req), outside they go to the root logger.

import { log } from '@astroscope/node/log';

log.info('handling request');
log.info({ userId: 123 }, 'user logged in');
log.error(err, 'operation failed');

const dbLog = log.child({ component: 'db' });

Entries logged before the logger is constructed (env loading, config, instrumentation) are buffered (cap 100) and replayed once construction completes — the original timestamp is kept as a bufferedTime field. If startup dies before the logger exists, the buffer is dumped to the console together with the error.

Request logging happens at the native handler on finish/close: real status code, response size, ttfb, aborted-vs-completed, and the route pattern. An incoming x-request-id header is passed through (and echoed on the response); otherwise a short id is generated.

Responses the adapter sends itself get a route label of their own: static for a file served from the client dir (public assets, prerendered pages, 200 and 304 alike), duplicate-slashes and trailing-slash for the path redirects, body-limit for a refused body, and a native mount's name for observability.

Reporting the route yourself

The route comes from the route astro matched, which is wrong for a middleware that serves a request astro has no page for — one that rewrites via next(url), or answers itself. Astro matches /404 there, so the request is logged and measured as /404. overrideRequestRoute lets the middleware report what it actually served:

import { overrideRequestRoute } from '@astroscope/node/log';
import type { MiddlewareHandler } from 'astro';

export const onRequest: MiddlewareHandler = (ctx, next) => {
  const page = lookupPage(ctx.url.pathname);

  if (!page) return next();

  overrideRequestRoute('/cms/pages/[id]');

  return next(`/cms/pages/${page.id}`);
};

This fixes the log line, the metric and the server span name together. Pass a templated label, not a concrete path, so metric cardinality stays bounded. An overridden route always wins over the one astro matched, regardless of middleware order.

The adapter itself emits exactly one info line on startup — server ready { host, port, health, bootMs, warmupMs, totalMs } — and draining / shutdown complete { drainMs } on the way down. Everything else is debug or error level.

Telemetry

node({ telemetry }) ships the standard bundle: NodeSDK, fetch instrumentation via undici (diagnostics-channel based — no --import needed), Node runtime + host metrics, and a Prometheus reader on 0.0.0.0:9464. Prod: on by default. Dev: off by default (opt in with telemetry: { dev: true }).

  • server spans start at the native handler with W3C context extraction; the route pattern and span name are enriched by the internal middleware
  • url.query carries the query string with the values of redactedQueryParams replaced by REDACTED, by default the signed-url credentials of OpenTelemetry's semantic conventions (exported as OTEL_REDACTED_QUERY_PARAMS); an application secret in a query string (an oauth code, a reset token) needs adding. The same applies to the query of extended request logs
  • request metrics: http.server.request.duration, http.server.active_requests, astro.action.duration (astro.action.name is the registry name, any other /_actions/ path counts as unknown); fetch metrics come from the undici instrumentation
  • astro.render.failures {http.route} counts renders that failed after the status went out; the response is truncated on the wire, the request log line reads request truncated at error level and the server span carries astro.response.truncated
  • the boot lifecycle gets startup (with boot/warmup/listen children) and shutdown (with drain/onShutdown) spans
  • trace exporters are driven by the standard OTEL_* env vars; without a configured OTLP endpoint, trace exporting defaults to none (no failing localhost exports)
  • OTEL_SDK_DISABLED=true turns the SDK off entirely
  • the other astroscope packages record through the same SDK and are no-ops without it — their instruments are documented with them: @astroscope/react, @astroscope/wormhole, @astroscope/i18n

There is no helper for tracing server-render sections: measuring a subtree's render would require buffering it, which disables streaming. Wrap frontmatter awaits with tracer.startActiveSpan() from @opentelemetry/api instead — that's where the time lives, and it nests under the request span without touching the stream.

Health checks

Register checks from anywhere in server code — typically onStartup:

import { registerHealthCheck } from '@astroscope/node/health';

export async function onStartup() {
  const db = await connectMongo();

  registerHealthCheck({ name: 'mongo', check: () => db.ping() });
}
  • probes flip in order: live during startup, ready only after listen(), draining begins the moment a shutdown signal arrives
  • a check fails by throwing or returning { status: 'unhealthy' }; failing required checks turn readyz unhealthy (optional: true checks don't affect it, timeout defaults to 5s)
  • registerHealthCheck returns an unregister function, but cleanup is optional: checks still registered after onShutdown are removed automatically
  • when no health runtime is active (dev mode, health: false), registration is a no-op — no dev/prod branching needed in boot files

Native mounts

For Node libraries that need the real (req, res) — oidc-provider's callback(), ACME challenge handlers — mount them on the adapter's server instead of faking Node objects inside astro middleware:

// src/boot.ts
import { mountNativeHandler } from '@astroscope/node/native';

export function onStartup() {
  mountNativeHandler({ prefix: '/oidc', name: 'oidc' }, getOidcProvider().callback());
}
  • the handler owns the response completely: matched requests are dispatched before static serving and never reach astro middleware or rendering, so machine endpoints need no csrf exclusions
  • the handler gets the real request and response: streaming bodies, socket.remoteAddress, set-cookie arrays
  • requests stay inside request logging and tracing; name becomes the route label for logs, metrics and span names
  • identical in dev
  • matching: prefix (segment-aware, longest wins) or a match(req) predicate consulted after prefixes, in registration order
  • returns an unregister function; mounts are cleared automatically after onShutdown (and between dev restart generations)
  • handler errors are logged and answered with a 500 (unless the handler already sent headers)

Guards

Astro has no route guards: middleware is global and sees only the route pattern, and a page's exports are not loaded before it runs. @astroscope/node/guards puts protection where the request lands, with the types to match: a guard either denies or proves something, and what it proves is required on locals for the guarded code only. App.Locals keeps the honest shape (user?: User).

// src/guards.ts
import { defineGuard, deny } from '@astroscope/node/guards';

export const authenticated = defineGuard({
  name: 'authenticated',
  check: (ctx) => ({ user: ctx.locals.user ?? deny('UNAUTHORIZED') }),
  page: (ctx) => ctx.redirect('/login'),
});

// runs after `authenticated`, with `ctx.locals.user` already required
export const admin = authenticated.extend({
  name: 'admin',
  check: (ctx) => {
    if (!ctx.locals.user.roles.admin) deny('FORBIDDEN');
  },
  page: (ctx) => ctx.rewrite('/404'),
});

deny(code, message?) throws an ActionError; the returned object is the proof. A guard may also load something once and hand it over typed (check: async (ctx, { projectId }) => ({ project: await loadProject(...) })), declaring the request fields it needs in input (a zod shape).

Actions take guards in place of Astro's defineAction (the no-astro-define-action lint rule enforces the import). Guards run after input validation; fields they declare are merged into the schema and stripped again before the handler:

import { defineAction } from '@astroscope/node/guards';
import { z } from 'astro/zod';
import { admin } from '../guards';

export const server = {
  rename: defineAction({
    input: z.object({ projectId: z.string(), name: z.string() }),
    guards: [admin],
    handler: async (input, ctx) => {
      ctx.locals.user; // User, not User | undefined
    },
  }),
};

Endpoints wrap the same way. The body is read only when a guard in the list declares input, then its fields come from a json or form body:

import { defineRoute } from '@astroscope/node/guards';
import { admin } from '../../guards';

export const POST = defineRoute({
  guards: [admin],
  handler: async (ctx) => Response.json({ by: ctx.locals.user.id }),
});

A page's frontmatter is the handler, so it asks and returns:

---
import { guard } from '@astroscope/node/guards';
import { admin } from '../guards';

const { denied, user } = await guard(Astro, [admin]);

if (denied) return denied;
---

After the check user is required, before it optional.

A denial answers per the request, on every surface: a browser navigation (text/html in Accept) gets the denying guard's page response, the login redirect above or a 404 rewrite, or the status with the message when the guard has none; any other caller gets { error: { code, message } } with the status. A denial raised by a parent in an extend() chain keeps the parent's page handler.

Whole areas are guarded from the middleware, with the same guards and the same denial responses:

// src/middleware.ts
import { createGuardMiddleware } from '@astroscope/node/guards';
import { sequence } from 'astro:middleware';
import { admin, authenticated } from './guards';
import { sessionMiddleware } from './session';

export const onRequest = sequence(
  sessionMiddleware,
  createGuardMiddleware([
    { match: [{ prefix: '/app' }], guards: [authenticated] },
    { match: [{ prefix: '/admin' }], guards: [admin] },
  ]),
);

Patterns are the exclude-pattern vocabulary (prefix, exact, suffix, includes, pattern). It is defence in depth, not a replacement for the page-level call: a middleware cannot narrow locals, so pages inside the area still call guard(Astro, [...]) for the typed proofs; a guard that passed runs once per request.

A server island renders through astro's own endpoint, outside a prefix rule for the page's path: a component that must not render for everyone calls guard(Astro, [...]) in its own frontmatter.

Available guards

The package ships the guards that depend on the request alone; the ones that depend on your session shape (authenticated, admin) stay in the app, and a package ships the guard for its own concern (captcha() from @astroscope/cap/server).

rateLimit({ max, window, key?, name? })

A fixed window per key, in memory and per process: the max-th request within window ms still passes, the next one is denied with TOO_MANY_REQUESTS (429) until the window ends. The key is the client address by default; key(ctx) picks another subject (a user id, a target email) or returns undefined to exempt a request. Per process means per replica.

Put it first in a list, so a flood is refused before a guard that costs something runs:

import { captcha } from '@astroscope/cap/server';
import { rateLimit } from '@astroscope/node/guards';

guards: [rateLimit({ max: 5, window: 60_000 }), captcha()],

Exclude patterns

@astroscope/node/excludes ships the shared exclude-pattern vocabulary for middlewares. Matching itself comes from @entwico/dash/match; ExcludePattern is the serializable subset of dash's StringPattern (exact / prefix / suffix / includes / pattern).

import { RECOMMENDED_EXCLUDES, shouldExclude, withExcluded } from '@astroscope/node/excludes';

// pre-defined sets: DEV_EXCLUDES (vite dev paths), ASTRO_STATIC_EXCLUDES (/_astro/, /_image, /_i18n/),
// STATIC_EXCLUDES (favicon, robots.txt, ...), RECOMMENDED_EXCLUDES (dev + astro internals)

// in your own middleware
if (shouldExclude(ctx, [...RECOMMENDED_EXCLUDES, { exact: '/health' }])) return next();

// or wrap a third-party middleware without built-in exclude support
export const onRequest = withExcluded(someExternalMiddleware(), RECOMMENDED_EXCLUDES);

For hot paths, compile the set once with createMatcher from @entwico/dash/match instead of scanning per request.

Island preloading

Without it, an island's JS loads as a chain: the browser fetches the component module, parses it, discovers its imports, fetches those, and so on. In production the adapter knows each island's chunks from the build and emits preload tags next to the island, so the browser fetches everything in parallel. Deferred islands (client:visible, client:idle, client:media) are preloaded just ahead of their hydration.

Always on in production; islands: false disables the preloading only.

Pre-compressed static serving

At build time, every compressible file in dist/client gets max-quality .br (brotli 11) and .gz (gzip 9) variants written next to it — variants that don't shrink the file are skipped. At request time the static handler negotiates Accept-Encoding and serves the best variant with the original's content-type, content-encoding, vary: accept-encoding and per-variant etags (304s included).

Embedded build tweaks

Always on, no configuration:

  • SSR sourcemaps — the server bundle gets sourcemaps for readable stack traces; client bundles stay unmapped so browsers can't fetch source

SSR effect stripping for React islands lives in @astroscope/react.

Image processing

Astro's default sharp service makes /_image an on-demand endpoint anyone can drive, so the adapter keeps image processing off unless you ask for it:

node({
  imageService: 'auto', // 'on' | 'off' | 'auto'
});
  • 'auto' (default) — on when the astro config sets an image.service other than astro's default sharp, off otherwise. An explicit sharpImageService() needs 'on'
  • 'on' — astro's default sharp service, no further config needed
  • 'off' — off even over an explicit image.service

Off is loud: any astro:assets use (<Image>, <Picture>, getImage(), markdown images) throws with an explanation, and /_image answers 404.

Options

node({
  // boot lifecycle; false disables it. Skipped automatically when no boot file exists.
  boot: {
    entry: 'src/boot.ts', // default: src/boot.ts or src/boot/index.ts
    watch: true, // dev: restart the dev server on boot-dependency changes
  },

  // Kubernetes-style probes on a separate port. Enabled by default in
  // production (never active in dev); false disables.
  health: {
    host: '0.0.0.0', // falls back to HEALTH_HOST env, then 0.0.0.0 (kubelet probes hit the pod IP)
    port: 9090, // falls back to HEALTH_PORT env, then 9090
  },

  // CSRF protection (origin check for POST/PUT/PATCH/DELETE with exclusions).
  // Enabled by default; false keeps astro's built-in checkOrigin instead.
  csrf: {
    exclude: [{ exact: '/api/auth/backchannel-logout' }],
  },

  // request logging at the native handler. Enabled by default in production;
  // false disables it (the log proxy keeps working).
  logging: {
    exclude: [{ prefix: '/internal/' }], // replaces RECOMMENDED_EXCLUDES
    extended: false, // query (redacted per redactedQueryParams)/headers/client address (may capture sensitive data)
    dev: false, // also log requests in dev (astro narrates them already)
  },

  // platform telemetry. Enabled by default in production, off in dev; false disables.
  telemetry: {
    exclude: [{ prefix: '/internal/' }], // replaces RECOMMENDED_EXCLUDES
    prometheus: { host: '0.0.0.0', port: 9464 }, // false disables the reader
    dev: false, // start the SDK in dev too (once per process)
  },

  // island dependency preloading (production-only); false disables it
  islands: false,

  // on-demand image processing: 'auto' = on only when image.service is set
  // in the astro config (see the Image processing section)
  imageService: 'auto',

  // query parameters redacted in server spans and extended request logs; replaces
  // OTEL_REDACTED_QUERY_PARAMS (the signed-url credentials of the OpenTelemetry conventions)
  redactedQueryParams: [...OTEL_REDACTED_QUERY_PARAMS, 'code', 'token'],

  bodySizeLimit: 1024 * 1024, // request body limit in bytes: 413 when announced larger, cut off when chunked
  shutdownTimeout: 10_000, // ms to wait for in-flight requests on shutdown; also bounds onShutdown and the telemetry flush, each
  keepAliveTimeout: 120_000, // ms an idle keep-alive connection stays open; must exceed the reverse proxy's idle timeout
});

HTTPS

The built server serves plain HTTP — in production, TLS is expected to terminate at the ingress. For local runs of the built server (e.g. auth flows that require a secure origin), set SERVER_CERT_PATH and SERVER_KEY_PATH to serve HTTPS directly — the same contract as @astrojs/node:

SERVER_CERT_PATH=./cert/tls.crt
SERVER_KEY_PATH=./cert/tls.key

Both must be set — startup fails if only one is present. Since env files load before the server starts listening, the variables may live in .env (or the file pointed to by CONFIG_PATH) instead of the shell environment.

This only affects the built server. The dev server is Vite's — configure vite.server.https in astro.config for HTTPS in dev.

Environment variables

VariableEffect
HOST / PORTOverride the listen address at runtime
SERVER_CERT_PATH / SERVER_KEY_PATHServe HTTPS with the given certificate/key (see HTTPS)
HEALTH_HOST / HEALTH_PORTOverride the health probe address (when not set in options)
CONFIG_PATHEnv file to load at startup (falls back to ./.env)
OTEL_EXPORTER_PROMETHEUS_HOST / OTEL_EXPORTER_PROMETHEUS_PORTOverride the Prometheus reader address
OTEL_SDK_DISABLED=trueDisable the telemetry SDK entirely
standard OTEL_*Exporter/resource configuration (e.g. OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME)
ASTROSCOPE_NODE_AUTOSTART=disabledBuild the entry without starting the server (exports startServer())

Shutdown sequence

On SIGTERM/SIGINT:

  1. readiness probe starts failing, draining is logged
  2. the server stops accepting connections and waits up to shutdownTimeout for in-flight requests
  3. the beforeOnShutdown listeners run, then onShutdown, then the afterOnShutdown listeners — bounded by shutdownTimeout; a hanging phase is logged and left behind
  4. shutdown complete { drainMs } is logged, the health server stops, telemetry flushes (bounded the same way), the process exits with code 0

License

MIT

版本与发布记录

@astroscope/node 共发布 21 个版本,首次发布于 2026/07/10;过去 12 个月发布了 21 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
5.0.1latest2026/11/034 天前
5.0.02026/11/034 天前
4.2.02026/11/025 天前
4.1.02026/10/1227 天前
4.0.02026/10/0929 天前
3.1.22026/09/231 个月前

关于 @astroscope/node 的常见问题

@astroscope/node 是什么?

Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probes, request logging, telemetry, CSRF and static serving run as plain code around server.listen()

如何安装 @astroscope/node?

在项目目录运行 npm install @astroscope/node,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vite(^8.0.0)、astro(^7.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@astroscope/node 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@astroscope/node 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@astroscope/node 可以商用吗?

@astroscope/node 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@astroscope/node 的下载量有多少?

根据 npm 官方下载统计,@astroscope/node 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,968 次,日均约 66 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@astroscope/node 还在维护吗?

最新版本 5.0.1 发布于 2026/11/03(4 天前);至今共发布 21 个版本,其中过去 12 个月发布了 21 个;首次发布于 2026/07/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。