Telemetry (#3256)
* feat: add @astrojs/telemetry * feat: add telemetry events, add queueing system * feat(telemetry): record CLI events * chore: add note * feat: support generic TELEMETRY_DISABLED env var * Fix test script * shim telemetry in tests * Shim telemetry in other commands * Stub telemetry in the memory leak test * Disable telemetry in smoke tests * Adds a changeset * Run the formatter * few updates * Include config keys * Add shallow viteKeys array: : * Add vite keys and tests Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
parent
48a35e6042
commit
f76038ac7d
28 changed files with 938 additions and 35 deletions
6
.changeset/lazy-phones-run.md
Normal file
6
.changeset/lazy-phones-run.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@astrojs/telemetry': minor
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Adds anonymous telemetry data to the cli
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -109,6 +109,8 @@ jobs:
|
|||
test:
|
||||
name: 'Test: ${{ matrix.os }} (node@${{ matrix.node_version }})'
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
ASTRO_TELEMETRY_DISABLED: true
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"@astrojs/language-server": "^0.13.4",
|
||||
"@astrojs/markdown-remark": "^0.9.2",
|
||||
"@astrojs/prism": "0.4.1",
|
||||
"@astrojs/telemetry": "^0.0.1",
|
||||
"@astrojs/webapi": "^0.11.1",
|
||||
"@babel/core": "^7.17.9",
|
||||
"@babel/generator": "^7.17.9",
|
||||
|
|
|
@ -6,6 +6,9 @@ import { LogOptions } from '../core/logger/core.js';
|
|||
import * as colors from 'kleur/colors';
|
||||
import yargs from 'yargs-parser';
|
||||
import { z } from 'zod';
|
||||
import { AstroTelemetry } from '@astrojs/telemetry';
|
||||
import * as event from '@astrojs/telemetry/events';
|
||||
|
||||
import { nodeLogDestination, enableVerboseLogging } from '../core/logger/node.js';
|
||||
import build from '../core/build/index.js';
|
||||
import add from '../core/add/index.js';
|
||||
|
@ -13,6 +16,7 @@ import devServer from '../core/dev/index.js';
|
|||
import preview from '../core/preview/index.js';
|
||||
import { check } from './check.js';
|
||||
import { openInBrowser } from './open.js';
|
||||
import * as telemetryHandler from './telemetry.js';
|
||||
import { loadConfig } from '../core/config.js';
|
||||
import { printHelp, formatErrorMessage, formatConfigErrorMessage } from '../core/messages.js';
|
||||
import { createSafeError } from '../core/util.js';
|
||||
|
@ -27,7 +31,8 @@ type CLICommand =
|
|||
| 'build'
|
||||
| 'preview'
|
||||
| 'reload'
|
||||
| 'check';
|
||||
| 'check'
|
||||
| 'telemetry';
|
||||
|
||||
/** Display --help flag */
|
||||
function printAstroHelp() {
|
||||
|
@ -41,6 +46,7 @@ function printAstroHelp() {
|
|||
['build', 'Build a pre-compiled production-ready site.'],
|
||||
['preview', 'Preview your build locally before deploying.'],
|
||||
['check', 'Check your project for errors.'],
|
||||
['telemetry', 'Enable/disable anonymous data collection.'],
|
||||
['--version', 'Show the version number and exit.'],
|
||||
['--help', 'Show this help message.'],
|
||||
],
|
||||
|
@ -67,6 +73,7 @@ async function printVersion() {
|
|||
function resolveCommand(flags: Arguments): CLICommand {
|
||||
const cmd = flags._[2] as string;
|
||||
if (cmd === 'add') return 'add';
|
||||
if (cmd === 'telemetry') return 'telemetry';
|
||||
if (flags.version) return 'version';
|
||||
else if (flags.help) return 'help';
|
||||
|
||||
|
@ -103,12 +110,28 @@ export async function cli(args: string[]) {
|
|||
} else if (flags.silent) {
|
||||
logging.level = 'silent';
|
||||
}
|
||||
const telemetry = new AstroTelemetry({ version: process.env.PACKAGE_VERSION ?? '' });
|
||||
|
||||
if (cmd === 'telemetry') {
|
||||
try {
|
||||
const subcommand = flags._[3]?.toString();
|
||||
return await telemetryHandler.update(subcommand, { flags, telemetry });
|
||||
} catch (err) {
|
||||
return throwAndExit(err);
|
||||
}
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 'add': {
|
||||
try {
|
||||
const packages = flags._.slice(3) as string[];
|
||||
return await add(packages, { cwd: root, flags, logging });
|
||||
telemetry.record(
|
||||
event.eventCliSession({
|
||||
astroVersion: process.env.PACKAGE_VERSION ?? '',
|
||||
cliCommand: 'add',
|
||||
})
|
||||
);
|
||||
return await add(packages, { cwd: root, flags, logging, telemetry });
|
||||
} catch (err) {
|
||||
return throwAndExit(err);
|
||||
}
|
||||
|
@ -116,7 +139,13 @@ export async function cli(args: string[]) {
|
|||
case 'dev': {
|
||||
try {
|
||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
||||
await devServer(config, { logging });
|
||||
telemetry.record(
|
||||
event.eventCliSession(
|
||||
{ astroVersion: process.env.PACKAGE_VERSION ?? '', cliCommand: 'dev' },
|
||||
config
|
||||
)
|
||||
);
|
||||
await devServer(config, { logging, telemetry });
|
||||
return await new Promise(() => {}); // lives forever
|
||||
} catch (err) {
|
||||
return throwAndExit(err);
|
||||
|
@ -126,7 +155,13 @@ export async function cli(args: string[]) {
|
|||
case 'build': {
|
||||
try {
|
||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
||||
return await build(config, { logging });
|
||||
telemetry.record(
|
||||
event.eventCliSession(
|
||||
{ astroVersion: process.env.PACKAGE_VERSION ?? '', cliCommand: 'build' },
|
||||
config
|
||||
)
|
||||
);
|
||||
return await build(config, { logging, telemetry });
|
||||
} catch (err) {
|
||||
return throwAndExit(err);
|
||||
}
|
||||
|
@ -134,6 +169,12 @@ export async function cli(args: string[]) {
|
|||
|
||||
case 'check': {
|
||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
||||
telemetry.record(
|
||||
event.eventCliSession(
|
||||
{ astroVersion: process.env.PACKAGE_VERSION ?? '', cliCommand: 'check' },
|
||||
config
|
||||
)
|
||||
);
|
||||
const ret = await check(config);
|
||||
return process.exit(ret);
|
||||
}
|
||||
|
@ -141,7 +182,13 @@ export async function cli(args: string[]) {
|
|||
case 'preview': {
|
||||
try {
|
||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
||||
const server = await preview(config, { logging });
|
||||
telemetry.record(
|
||||
event.eventCliSession(
|
||||
{ astroVersion: process.env.PACKAGE_VERSION ?? '', cliCommand: 'preview' },
|
||||
config
|
||||
)
|
||||
);
|
||||
const server = await preview(config, { logging, telemetry });
|
||||
return await server.closed(); // keep alive until the server is closed
|
||||
} catch (err) {
|
||||
return throwAndExit(err);
|
||||
|
@ -150,6 +197,12 @@ export async function cli(args: string[]) {
|
|||
|
||||
case 'docs': {
|
||||
try {
|
||||
await telemetry.record(
|
||||
event.eventCliSession({
|
||||
astroVersion: process.env.PACKAGE_VERSION ?? '',
|
||||
cliCommand: 'docs',
|
||||
})
|
||||
);
|
||||
return await openInBrowser('https://docs.astro.build/');
|
||||
} catch (err) {
|
||||
return throwAndExit(err);
|
||||
|
|
47
packages/astro/src/cli/telemetry.ts
Normal file
47
packages/astro/src/cli/telemetry.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* eslint-disable no-console */
|
||||
import type yargs from 'yargs-parser';
|
||||
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||
|
||||
import prompts from 'prompts';
|
||||
import * as msg from '../core/messages.js';
|
||||
|
||||
export interface TelemetryOptions {
|
||||
flags: yargs.Arguments;
|
||||
telemetry: AstroTelemetry;
|
||||
}
|
||||
|
||||
export async function update(subcommand: string, { flags, telemetry }: TelemetryOptions) {
|
||||
const isValid = ['enable', 'disable', 'reset'].includes(subcommand);
|
||||
|
||||
if (flags.help || !isValid) {
|
||||
msg.printHelp({
|
||||
commandName: 'astro telemetry',
|
||||
usage: '<enable|disable|reset>',
|
||||
commands: [
|
||||
['enable', 'Enable anonymous data collection.'],
|
||||
['disable', 'Disable anonymous data collection.'],
|
||||
['reset', 'Reset anonymous data collection settings.'],
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case 'enable': {
|
||||
telemetry.setEnabled(true);
|
||||
console.log(msg.telemetryEnabled());
|
||||
return;
|
||||
}
|
||||
case 'disable': {
|
||||
telemetry.setEnabled(false);
|
||||
console.log(msg.telemetryDisabled());
|
||||
return;
|
||||
}
|
||||
case 'reset': {
|
||||
telemetry.clear();
|
||||
console.log(msg.telemetryReset());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import type yargs from 'yargs-parser';
|
||||
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||
import path from 'path';
|
||||
import { existsSync, promises as fs } from 'fs';
|
||||
import { execa } from 'execa';
|
||||
|
@ -24,6 +25,7 @@ import { appendForwardSlash } from '../path.js';
|
|||
export interface AddOptions {
|
||||
logging: LogOptions;
|
||||
flags: yargs.Arguments;
|
||||
telemetry: AstroTelemetry;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
|
@ -33,7 +35,7 @@ export interface IntegrationInfo {
|
|||
dependencies: [name: string, version: string][];
|
||||
}
|
||||
|
||||
export default async function add(names: string[], { cwd, flags, logging }: AddOptions) {
|
||||
export default async function add(names: string[], { cwd, flags, logging, telemetry }: AddOptions) {
|
||||
if (flags.help) {
|
||||
printHelp({
|
||||
commandName: 'astro add',
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { AstroConfig, BuildConfig, ManifestData } from '../../@types/astro';
|
||||
import type { LogOptions } from '../logger/core';
|
||||
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||
|
||||
import fs from 'fs';
|
||||
import * as colors from 'kleur/colors';
|
||||
|
@ -33,13 +34,11 @@ import { fixViteErrorMessage } from '../errors.js';
|
|||
export interface BuildOptions {
|
||||
mode?: string;
|
||||
logging: LogOptions;
|
||||
telemetry: AstroTelemetry;
|
||||
}
|
||||
|
||||
/** `astro build` */
|
||||
export default async function build(
|
||||
config: AstroConfig,
|
||||
options: BuildOptions = { logging: nodeLogOptions }
|
||||
): Promise<void> {
|
||||
export default async function build(config: AstroConfig, options: BuildOptions): Promise<void> {
|
||||
applyPolyfill();
|
||||
const builder = new AstroBuilder(config, options);
|
||||
await builder.run();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { AddressInfo } from 'net';
|
||||
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||
import { performance } from 'perf_hooks';
|
||||
import * as vite from 'vite';
|
||||
import type { AstroConfig } from '../../@types/astro';
|
||||
|
@ -17,6 +18,7 @@ import { apply as applyPolyfill } from '../polyfill.js';
|
|||
|
||||
export interface DevOptions {
|
||||
logging: LogOptions;
|
||||
telemetry: AstroTelemetry;
|
||||
}
|
||||
|
||||
export interface DevServer {
|
||||
|
@ -25,12 +27,10 @@ export interface DevServer {
|
|||
}
|
||||
|
||||
/** `astro dev` */
|
||||
export default async function dev(
|
||||
config: AstroConfig,
|
||||
options: DevOptions = { logging: nodeLogOptions }
|
||||
): Promise<DevServer> {
|
||||
export default async function dev(config: AstroConfig, options: DevOptions): Promise<DevServer> {
|
||||
const devStart = performance.now();
|
||||
applyPolyfill();
|
||||
await options.telemetry.record([]);
|
||||
config = await runHookConfigSetup({ config, command: 'dev' });
|
||||
const { host, port } = config.server;
|
||||
const viteConfig = await createVite(
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* Dev server messages (organized here to prevent clutter)
|
||||
*/
|
||||
|
||||
import type { AddressInfo } from 'net';
|
||||
import type { AstroConfig } from '../@types/astro';
|
||||
import os from 'os';
|
||||
import {
|
||||
bold,
|
||||
dim,
|
||||
|
@ -15,10 +14,9 @@ import {
|
|||
black,
|
||||
bgRed,
|
||||
bgWhite,
|
||||
bgCyan,
|
||||
} from 'kleur/colors';
|
||||
import os from 'os';
|
||||
import type { AddressInfo } from 'net';
|
||||
import type { AstroConfig } from '../@types/astro';
|
||||
import boxen from 'boxen';
|
||||
import { collectErrorMetadata, cleanErrorStack } from './errors.js';
|
||||
import { ZodError } from 'zod';
|
||||
import { emoji, getLocalAddress, padMultilineString } from './util.js';
|
||||
|
@ -116,6 +114,37 @@ export function devStart({
|
|||
return messages.map((msg) => ` ${msg}`).join('\n');
|
||||
}
|
||||
|
||||
export function telemetryNotice() {
|
||||
const headline = yellow(`Astro now collects ${bold('anonymous')} usage data.`);
|
||||
const why = `This ${bold('optional program')} will help shape our roadmap.`;
|
||||
const more = `For more info, visit ${underline('https://astro.build/telemetry')}`;
|
||||
const box = boxen([headline, why, '', more].join('\n'), {
|
||||
margin: 0,
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow',
|
||||
});
|
||||
return box;
|
||||
}
|
||||
|
||||
export function telemetryEnabled() {
|
||||
return `\n ${green('◉')} Anonymous telemetry is ${bgGreen(
|
||||
black(' enabled ')
|
||||
)}. Thank you for improving Astro!\n`;
|
||||
}
|
||||
|
||||
export function telemetryDisabled() {
|
||||
return `\n ${yellow('◯')} Anonymous telemetry is ${bgYellow(
|
||||
black(' disabled ')
|
||||
)}. We won't share any usage data.\n`;
|
||||
}
|
||||
|
||||
export function telemetryReset() {
|
||||
return `\n ${cyan('◆')} Anonymous telemetry has been ${bgCyan(
|
||||
black(' reset ')
|
||||
)}. You may be prompted again.\n`;
|
||||
}
|
||||
|
||||
export function prerelease({ currentVersion }: { currentVersion: string }) {
|
||||
const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '');
|
||||
const badge = bgYellow(black(` ${tag} `));
|
||||
|
@ -227,7 +256,7 @@ export function printHelp({
|
|||
for (const row of rows) {
|
||||
raw += `${opts.prefix}${bold(`${row[0]}`.padStart(opts.padding - opts.prefix.length))}`;
|
||||
if (split) raw += '\n ';
|
||||
raw += dim(row[1]) + '\n';
|
||||
raw += ' ' + dim(row[1]) + '\n';
|
||||
}
|
||||
|
||||
return raw.slice(0, -1); // remove latest \n
|
||||
|
@ -252,7 +281,7 @@ export function printHelp({
|
|||
message.push(
|
||||
linebreak(),
|
||||
title('Commands'),
|
||||
table(commands, { padding: 28, prefix: ' astro ' })
|
||||
table(commands, { padding: 28, prefix: ` ${commandName || 'astro'} ` })
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { AstroConfig } from '../../@types/astro';
|
||||
import type { LogOptions } from '../logger/core';
|
||||
import type { AddressInfo } from 'net';
|
||||
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||
|
||||
import http from 'http';
|
||||
import sirv from 'sirv';
|
||||
import { performance } from 'perf_hooks';
|
||||
|
@ -12,6 +14,7 @@ import { getResolvedHostForHttpServer } from './util.js';
|
|||
|
||||
interface PreviewOptions {
|
||||
logging: LogOptions;
|
||||
telemetry: AstroTelemetry;
|
||||
}
|
||||
|
||||
export interface PreviewServer {
|
||||
|
|
|
@ -86,10 +86,17 @@ export async function loadFixture(inlineConfig) {
|
|||
level: 'error',
|
||||
};
|
||||
|
||||
/** @type {import('@astrojs/telemetry').AstroTelemetry} */
|
||||
const telemetry = {
|
||||
record() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
build: (opts = {}) => build(config, { mode: 'development', logging, ...opts }),
|
||||
build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }),
|
||||
startDevServer: async (opts = {}) => {
|
||||
const devResult = await dev(config, { logging, ...opts });
|
||||
const devResult = await dev(config, { logging, telemetry, ...opts });
|
||||
config.server.port = devResult.address.port; // update port
|
||||
return devResult;
|
||||
},
|
||||
|
@ -97,7 +104,7 @@ export async function loadFixture(inlineConfig) {
|
|||
fetch: (url, init) =>
|
||||
fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init),
|
||||
preview: async (opts = {}) => {
|
||||
const previewServer = await preview(config, { logging, ...opts });
|
||||
const previewServer = await preview(config, { logging, telemetry, ...opts });
|
||||
return previewServer;
|
||||
},
|
||||
readFile: (filePath) =>
|
||||
|
|
9
packages/telemetry/README.md
Normal file
9
packages/telemetry/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Astro Telemetry
|
||||
|
||||
This package is used to collect anonymous telemetry data within the Astro CLI. Telemetry data does not contain any personal identifying information and can be disabled via:
|
||||
|
||||
```shell
|
||||
astro telemetry disable
|
||||
```
|
||||
|
||||
See the [CLI documentation](https://docs.astro.build/en/reference/cli-reference/#astro-telemetry) for more options on configuration telemetry.
|
1
packages/telemetry/events.d.ts
vendored
Normal file
1
packages/telemetry/events.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './dist/types/events';
|
47
packages/telemetry/package.json
Normal file
47
packages/telemetry/package.json
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "@astrojs/telemetry",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"author": "withastro",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/withastro/astro.git",
|
||||
"directory": "packages/telemetry"
|
||||
},
|
||||
"bugs": "https://github.com/withastro/astro/issues",
|
||||
"homepage": "https://astro.build",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./events": "./dist/events/index.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
||||
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
||||
"test": "mocha --exit --timeout 20000 test/"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"ci-info": "^3.3.0",
|
||||
"debug": "^4.3.4",
|
||||
"dlv": "^1.1.3",
|
||||
"dset": "^3.1.1",
|
||||
"escalade": "^3.1.1",
|
||||
"is-docker": "^3.0.0",
|
||||
"is-wsl": "^2.2.0",
|
||||
"node-fetch": "^3.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dlv": "^1.1.2",
|
||||
"@types/node": "^14.18.13",
|
||||
"astro-scripts": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || >=16.0.0"
|
||||
}
|
||||
}
|
48
packages/telemetry/src/anonymous-meta.ts
Normal file
48
packages/telemetry/src/anonymous-meta.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import os from 'node:os';
|
||||
import isDocker from 'is-docker';
|
||||
import isWSL from 'is-wsl';
|
||||
import { isCI, name as ciName } from 'ci-info';
|
||||
|
||||
type AnonymousMeta = {
|
||||
systemPlatform: NodeJS.Platform;
|
||||
systemRelease: string;
|
||||
systemArchitecture: string;
|
||||
cpuCount: number;
|
||||
cpuModel: string | null;
|
||||
cpuSpeed: number | null;
|
||||
memoryInMb: number;
|
||||
isDocker: boolean;
|
||||
isWSL: boolean;
|
||||
isCI: boolean;
|
||||
ciName: string | null;
|
||||
astroVersion: string;
|
||||
};
|
||||
|
||||
let meta: AnonymousMeta | undefined;
|
||||
|
||||
export function getAnonymousMeta(astroVersion: string): AnonymousMeta {
|
||||
if (meta) {
|
||||
return meta;
|
||||
}
|
||||
|
||||
const cpus = os.cpus() || [];
|
||||
meta = {
|
||||
// Software information
|
||||
systemPlatform: os.platform(),
|
||||
systemRelease: os.release(),
|
||||
systemArchitecture: os.arch(),
|
||||
// Machine information
|
||||
cpuCount: cpus.length,
|
||||
cpuModel: cpus.length ? cpus[0].model : null,
|
||||
cpuSpeed: cpus.length ? cpus[0].speed : null,
|
||||
memoryInMb: Math.trunc(os.totalmem() / Math.pow(1024, 2)),
|
||||
// Environment information
|
||||
isDocker: isDocker(),
|
||||
isWSL,
|
||||
isCI,
|
||||
ciName,
|
||||
astroVersion,
|
||||
};
|
||||
|
||||
return meta!;
|
||||
}
|
89
packages/telemetry/src/config.ts
Normal file
89
packages/telemetry/src/config.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import process from 'node:process';
|
||||
import { dset } from 'dset';
|
||||
import dget from 'dlv';
|
||||
|
||||
export interface ConfigOptions {
|
||||
name: string;
|
||||
defaults: Map<string, any>;
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/sindresorhus/env-paths
|
||||
function getConfigDir(name: string) {
|
||||
const homedir = os.homedir();
|
||||
const macos = () => path.join(homedir, 'Library', 'Preferences', name);
|
||||
const win = () => {
|
||||
const { APPDATA = path.join(homedir, 'AppData', 'Roaming') } = process.env;
|
||||
return path.join(APPDATA, name, 'Config');
|
||||
};
|
||||
const linux = () => {
|
||||
const { XDG_CONFIG_HOME = path.join(homedir, '.config') } = process.env;
|
||||
return path.join(XDG_CONFIG_HOME, name);
|
||||
};
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return macos();
|
||||
case 'win32':
|
||||
return win();
|
||||
default:
|
||||
return linux();
|
||||
}
|
||||
}
|
||||
|
||||
export class Config {
|
||||
private dir: string;
|
||||
private file: string;
|
||||
|
||||
constructor(private project: ConfigOptions) {
|
||||
this.dir = getConfigDir(this.project.name);
|
||||
this.file = path.join(this.dir, 'config.json');
|
||||
}
|
||||
|
||||
private _store?: Record<string, any>;
|
||||
private get store(): Record<string, any> {
|
||||
if (this._store) return this._store;
|
||||
this.ensureDir();
|
||||
if (fs.existsSync(this.file)) {
|
||||
this._store = JSON.parse(fs.readFileSync(this.file).toString());
|
||||
} else {
|
||||
const store = {};
|
||||
for (const [key, value] of this.project.defaults) {
|
||||
dset(store, key, value);
|
||||
}
|
||||
this._store = store;
|
||||
this.write();
|
||||
}
|
||||
return this._store!;
|
||||
}
|
||||
private set store(value: Record<string, any>) {
|
||||
this._store = value;
|
||||
this.write();
|
||||
}
|
||||
private ensureDir() {
|
||||
fs.mkdirSync(this.dir, { recursive: true });
|
||||
}
|
||||
write() {
|
||||
fs.writeFileSync(this.file, JSON.stringify(this.store, null, '\t'));
|
||||
}
|
||||
clear(): void {
|
||||
this.store = {};
|
||||
fs.rmSync(this.file, { recursive: true });
|
||||
}
|
||||
delete(key: string): boolean {
|
||||
dset(this.store, key, undefined);
|
||||
this.write();
|
||||
return true;
|
||||
}
|
||||
get(key: string): any {
|
||||
return dget(this.store, key);
|
||||
}
|
||||
has(key: string): boolean {
|
||||
return typeof this.get(key) !== 'undefined';
|
||||
}
|
||||
set(key: string, value: any): void {
|
||||
dset(this.store, key, value);
|
||||
this.write();
|
||||
}
|
||||
}
|
2
packages/telemetry/src/events/build.ts
Normal file
2
packages/telemetry/src/events/build.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
// See https://github.com/vercel/next.js/blob/canary/packages/next/telemetry/events/build.ts
|
||||
export {};
|
2
packages/telemetry/src/events/index.ts
Normal file
2
packages/telemetry/src/events/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './session.js';
|
||||
export * from './build.js';
|
96
packages/telemetry/src/events/session.ts
Normal file
96
packages/telemetry/src/events/session.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
import escalade from 'escalade/sync';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const EVENT_SESSION = 'ASTRO_CLI_SESSION_STARTED';
|
||||
|
||||
interface EventCliSession {
|
||||
astroVersion: string;
|
||||
cliCommand: string;
|
||||
}
|
||||
|
||||
interface ConfigInfo {
|
||||
hasViteConfig: boolean;
|
||||
hasBase: boolean;
|
||||
viteKeys: string[];
|
||||
markdownPlugins: string[];
|
||||
adapter: string | null;
|
||||
integrations: string[];
|
||||
experimentalFeatures: string[];
|
||||
}
|
||||
|
||||
interface EventCliSessionInternal extends EventCliSession {
|
||||
nodeVersion: string;
|
||||
viteVersion: string;
|
||||
config?: ConfigInfo;
|
||||
}
|
||||
|
||||
function getViteVersion() {
|
||||
try {
|
||||
const { version } = require('vite/package.json');
|
||||
return version;
|
||||
} catch (e) {}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getExperimentalFeatures(astroConfig?: Record<string, any>): string[] | undefined {
|
||||
if (!astroConfig) return undefined;
|
||||
return Object.entries(astroConfig.experimental || []).reduce((acc, [key, value]) => {
|
||||
if (value) {
|
||||
acc.push(key);
|
||||
}
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
}
|
||||
|
||||
const secondLevelViteKeys = new Set(["resolve", "css", "json", "server", "server.fs", "build", "preview", "optimizeDeps", "ssr", "worker"]);
|
||||
function viteConfigKeys(obj: Record<string, any> | undefined, parentKey: string): string[] {
|
||||
if(!obj) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(obj).map(([key, value]) => {
|
||||
if(typeof value === 'object' && !Array.isArray(value)) {
|
||||
const localKey = parentKey ? parentKey + '.' + key : key;
|
||||
if(secondLevelViteKeys.has(localKey)) {
|
||||
let keys = viteConfigKeys(value, localKey).map(subkey => key + '.' + subkey);
|
||||
keys.unshift(key);
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}).flat(1);
|
||||
}
|
||||
|
||||
export function eventCliSession(
|
||||
event: EventCliSession,
|
||||
astroConfig?: Record<string, any>
|
||||
): { eventName: string; payload: EventCliSessionInternal }[] {
|
||||
const payload: EventCliSessionInternal = {
|
||||
cliCommand: event.cliCommand,
|
||||
// Versions
|
||||
astroVersion: event.astroVersion,
|
||||
viteVersion: getViteVersion(),
|
||||
nodeVersion: process.version.replace(/^v?/, ''),
|
||||
// Config Values
|
||||
config: astroConfig
|
||||
? {
|
||||
hasViteConfig: Object.keys(astroConfig?.vite).length > 0,
|
||||
markdownPlugins:
|
||||
[
|
||||
astroConfig?.markdown?.remarkPlugins ?? [],
|
||||
astroConfig?.markdown?.rehypePlugins ?? [],
|
||||
].flat(1),
|
||||
hasBase: astroConfig?.base !== '/',
|
||||
viteKeys: viteConfigKeys(astroConfig?.vite, ''),
|
||||
adapter: astroConfig?.adapter?.name ?? null,
|
||||
integrations: astroConfig?.integrations?.map((i: any) => i.name) ?? [],
|
||||
experimentalFeatures: getExperimentalFeatures(astroConfig) ?? [],
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
return [{ eventName: EVENT_SESSION, payload }];
|
||||
}
|
170
packages/telemetry/src/index.ts
Normal file
170
packages/telemetry/src/index.ts
Normal file
|
@ -0,0 +1,170 @@
|
|||
import type { BinaryLike } from 'node:crypto';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
|
||||
import { isCI } from 'ci-info';
|
||||
import debug from 'debug';
|
||||
|
||||
import * as KEY from './keys.js';
|
||||
import { post } from './post.js';
|
||||
import { getAnonymousMeta } from './anonymous-meta.js';
|
||||
import { getRawProjectId } from './project-id.js';
|
||||
import { Config } from './config.js';
|
||||
|
||||
export interface AstroTelemetryOptions {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export type TelemetryEvent = { eventName: string; payload: Record<string, any> };
|
||||
|
||||
interface EventContext {
|
||||
anonymousId: string;
|
||||
projectId: string;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export class AstroTelemetry {
|
||||
private rawProjectId = getRawProjectId();
|
||||
private sessionId = randomBytes(32).toString('hex');
|
||||
private config = new Config({
|
||||
name: 'astro',
|
||||
// Use getter to defer generation of defaults unless needed
|
||||
get defaults() {
|
||||
return new Map<string, any>([
|
||||
[KEY.TELEMETRY_ENABLED, true],
|
||||
[KEY.TELEMETRY_SALT, randomBytes(16).toString('hex')],
|
||||
[KEY.TELEMETRY_ID, randomBytes(32).toString('hex')],
|
||||
]);
|
||||
},
|
||||
});
|
||||
private debug = debug('astro:telemetry');
|
||||
|
||||
private get astroVersion() {
|
||||
return this.opts.version;
|
||||
}
|
||||
private get ASTRO_TELEMETRY_DISABLED() {
|
||||
return process.env.ASTRO_TELEMETRY_DISABLED;
|
||||
}
|
||||
private get TELEMETRY_DISABLED() {
|
||||
return process.env.TELEMETRY_DISABLED;
|
||||
}
|
||||
|
||||
constructor(private opts: AstroTelemetryOptions) {
|
||||
// When the process exits, flush any queued promises
|
||||
process.on('SIGINT', () => this.flush());
|
||||
}
|
||||
|
||||
// Util to get value from config or set it if missing
|
||||
private getWithFallback<T>(key: string, value: T): T {
|
||||
const val = this.config.get(key);
|
||||
if (val) {
|
||||
return val;
|
||||
}
|
||||
this.config.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private get salt(): string {
|
||||
return this.getWithFallback(KEY.TELEMETRY_SALT, randomBytes(16).toString('hex'));
|
||||
}
|
||||
private get enabled(): boolean {
|
||||
return this.getWithFallback(KEY.TELEMETRY_ENABLED, true);
|
||||
}
|
||||
private get anonymousId(): string {
|
||||
return this.getWithFallback(KEY.TELEMETRY_ID, randomBytes(32).toString('hex'));
|
||||
}
|
||||
private get notifyDate(): string {
|
||||
return this.getWithFallback(KEY.TELEMETRY_NOTIFY_DATE, '');
|
||||
}
|
||||
|
||||
// Create a ONE-WAY hash so there is no way for Astro to decode the value later.
|
||||
private oneWayHash(payload: BinaryLike): string {
|
||||
const hash = createHash('sha256');
|
||||
// Always prepend the payload value with salt! This ensures the hash is one-way.
|
||||
hash.update(this.salt);
|
||||
hash.update(payload);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
// Instead of sending `rawProjectId`, we only ever reference a hashed value *derived*
|
||||
// from `rawProjectId`. This ensures that `projectId` is ALWAYS anonymous and can't
|
||||
// be reversed from the hashed value.
|
||||
private get projectId(): string {
|
||||
return this.oneWayHash(this.rawProjectId);
|
||||
}
|
||||
|
||||
private get isDisabled(): boolean {
|
||||
if (Boolean(this.ASTRO_TELEMETRY_DISABLED || this.TELEMETRY_DISABLED)) {
|
||||
return true;
|
||||
}
|
||||
return this.enabled === false;
|
||||
}
|
||||
|
||||
setEnabled(value: boolean) {
|
||||
this.config.set(KEY.TELEMETRY_ENABLED, value);
|
||||
}
|
||||
|
||||
clear() {
|
||||
return this.config.clear();
|
||||
}
|
||||
|
||||
private queue: Promise<any>[] = [];
|
||||
|
||||
// Wait for any in-flight promises to resolve
|
||||
private async flush() {
|
||||
await Promise.all(this.queue);
|
||||
}
|
||||
|
||||
async notify(callback: () => Promise<boolean>) {
|
||||
if (this.isDisabled || isCI) {
|
||||
return;
|
||||
}
|
||||
// The end-user has already been notified about our telemetry integration!
|
||||
// Don't bother them about it again.
|
||||
// In the event of significant changes, we should invalidate old dates.
|
||||
if (this.notifyDate) {
|
||||
return;
|
||||
}
|
||||
const enabled = await callback();
|
||||
this.config.set(KEY.TELEMETRY_NOTIFY_DATE, Date.now().toString());
|
||||
this.config.set(KEY.TELEMETRY_ENABLED, enabled);
|
||||
}
|
||||
|
||||
async record(event: TelemetryEvent | TelemetryEvent[] = []) {
|
||||
const events: TelemetryEvent[] = Array.isArray(event) ? event : [event];
|
||||
if (events.length < 1) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.debug.enabled) {
|
||||
// Print to standard error to simplify selecting the output
|
||||
events.forEach(({ eventName, payload }) =>
|
||||
this.debug(JSON.stringify({ eventName, payload }, null, 2))
|
||||
);
|
||||
// Do not send the telemetry data if debugging. Users may use this feature
|
||||
// to preview what data would be sent.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Skip recording telemetry if the feature is disabled
|
||||
if (this.isDisabled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const context: EventContext = {
|
||||
anonymousId: this.anonymousId,
|
||||
projectId: this.projectId,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
const meta = getAnonymousMeta(this.astroVersion);
|
||||
|
||||
const req = post({
|
||||
context,
|
||||
meta,
|
||||
events,
|
||||
}).then(() => {
|
||||
this.queue = this.queue.filter((r) => r !== req);
|
||||
});
|
||||
this.queue.push(req);
|
||||
return req;
|
||||
}
|
||||
}
|
16
packages/telemetry/src/keys.ts
Normal file
16
packages/telemetry/src/keys.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
// This is the key that stores whether or not telemetry is enabled or disabled.
|
||||
export const TELEMETRY_ENABLED = 'telemetry.enabled';
|
||||
|
||||
// This is the key that specifies when the user was informed about anonymous
|
||||
// telemetry collection.
|
||||
export const TELEMETRY_NOTIFY_DATE = 'telemetry.notifiedAt';
|
||||
|
||||
// This is a quasi-persistent identifier used to dedupe recurring events. It's
|
||||
// generated from random data and completely anonymous.
|
||||
export const TELEMETRY_ID = `telemetry.anonymousId`;
|
||||
|
||||
// This is the cryptographic salt that is included within every hashed value.
|
||||
// This salt value is never sent to us, ensuring privacy and the one-way nature
|
||||
// of the hash (prevents dictionary lookups of pre-computed hashes).
|
||||
// See the `oneWayHash` function.
|
||||
export const TELEMETRY_SALT = `telemetry.salt`;
|
13
packages/telemetry/src/post.ts
Normal file
13
packages/telemetry/src/post.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import fetch from 'node-fetch';
|
||||
const ASTRO_TELEMETRY_ENDPOINT = `https://telemetry.astro.build/api/v1/record`;
|
||||
const noop = () => {};
|
||||
|
||||
export function post(body: Record<string, any>) {
|
||||
return fetch(ASTRO_TELEMETRY_ENDPOINT, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
.catch(noop)
|
||||
.then(noop, noop);
|
||||
}
|
27
packages/telemetry/src/project-id.ts
Normal file
27
packages/telemetry/src/project-id.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { execSync } from 'child_process';
|
||||
|
||||
// Why does Astro need a project ID? Why is it looking at my git remote?
|
||||
// ---
|
||||
// Astro's telemetry is and always will be completely anonymous.
|
||||
// Differentiating unique projects helps us track feature usage accurately.
|
||||
//
|
||||
// We **never** read your actual git remote! The value is hashed one-way
|
||||
// with random salt data, making it impossible for us to reverse or try to
|
||||
// guess the remote by re-computing hashes.
|
||||
|
||||
function getProjectIdFromGit() {
|
||||
try {
|
||||
const originBuffer = execSync(`git config --local --get remote.origin.url`, {
|
||||
timeout: 1000,
|
||||
stdio: `pipe`,
|
||||
});
|
||||
|
||||
return String(originBuffer).trim();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRawProjectId(): string {
|
||||
return getProjectIdFromGit() ?? process.env.REPOSITORY_URL ?? process.cwd();
|
||||
}
|
181
packages/telemetry/test/session-event.test.js
Normal file
181
packages/telemetry/test/session-event.test.js
Normal file
|
@ -0,0 +1,181 @@
|
|||
import { expect } from 'chai';
|
||||
import * as events from '../dist/events/index.js';
|
||||
import { resolveConfig } from '../../astro/dist/core/config.js';
|
||||
|
||||
async function mockConfig(userConfig) {
|
||||
return await resolveConfig(userConfig, import.meta.url, {}, 'dev');
|
||||
}
|
||||
|
||||
describe('Session event', () => {
|
||||
it('top-level keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
css: { modules: [] },
|
||||
base: 'a',
|
||||
mode: 'b',
|
||||
define: {
|
||||
a: 'b',
|
||||
},
|
||||
publicDir: 'some/dir',
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['css', 'css.modules', 'base', 'mode', 'define', 'publicDir']);
|
||||
})
|
||||
|
||||
it('vite.resolve keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
a: 'b'
|
||||
},
|
||||
dedupe: ['one', 'two']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['resolve', 'resolve.alias', 'resolve.dedupe']);
|
||||
});
|
||||
|
||||
it('vite.css keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
resolve: {
|
||||
dedupe: ['one', 'two']
|
||||
},
|
||||
css: {
|
||||
modules: [],
|
||||
postcss: {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['resolve', 'resolve.dedupe', 'css', 'css.modules', 'css.postcss']);
|
||||
});
|
||||
|
||||
it('vite.server keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
server: {
|
||||
host: 'example.com',
|
||||
open: true,
|
||||
fs: {
|
||||
strict: true,
|
||||
allow: ['a', 'b']
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['server', 'server.host', 'server.open', 'server.fs', 'server.fs.strict', 'server.fs.allow']);
|
||||
});
|
||||
|
||||
it('vite.build keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
build: {
|
||||
target: 'one',
|
||||
outDir: 'some/dir',
|
||||
cssTarget: {
|
||||
one: 'two'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['build', 'build.target', 'build.outDir', 'build.cssTarget']);
|
||||
});
|
||||
|
||||
|
||||
it('vite.preview keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
preview: {
|
||||
host: 'example.com',
|
||||
port: 8080,
|
||||
another: {
|
||||
a: 'b'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['preview', 'preview.host', 'preview.port', 'preview.another']);
|
||||
});
|
||||
|
||||
it('vite.optimizeDeps keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
optimizeDeps: {
|
||||
entries: ['one', 'two'],
|
||||
exclude: ['secret', 'name']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['optimizeDeps', 'optimizeDeps.entries', 'optimizeDeps.exclude']);
|
||||
});
|
||||
|
||||
it('vite.ssr keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
ssr: {
|
||||
external: ['a'],
|
||||
target: { one: 'two' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['ssr', 'ssr.external', 'ssr.target']);
|
||||
});
|
||||
|
||||
it('vite.worker keys are captured', async () => {
|
||||
const config = await mockConfig({
|
||||
vite: {
|
||||
worker: {
|
||||
format: { a: 'b' },
|
||||
plugins: ['a', 'b']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [{ payload }] = events.eventCliSession({
|
||||
cliCommand: 'dev',
|
||||
astroVersion: '0.0.0'
|
||||
}, config);
|
||||
expect(payload.config.viteKeys).is.deep.equal(['worker', 'worker.format', 'worker.plugins']);
|
||||
});
|
||||
});
|
11
packages/telemetry/tsconfig.json
Normal file
11
packages/telemetry/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "./dist/types"
|
||||
}
|
||||
}
|
|
@ -461,6 +461,7 @@ importers:
|
|||
'@astrojs/language-server': ^0.13.4
|
||||
'@astrojs/markdown-remark': ^0.9.2
|
||||
'@astrojs/prism': 0.4.1
|
||||
'@astrojs/telemetry': ^0.0.1
|
||||
'@astrojs/webapi': ^0.11.1
|
||||
'@babel/core': ^7.17.9
|
||||
'@babel/generator': ^7.17.9
|
||||
|
@ -546,6 +547,7 @@ importers:
|
|||
'@astrojs/language-server': 0.13.4
|
||||
'@astrojs/markdown-remark': link:../markdown/remark
|
||||
'@astrojs/prism': link:../astro-prism
|
||||
'@astrojs/telemetry': link:../telemetry
|
||||
'@astrojs/webapi': link:../webapi
|
||||
'@babel/core': 7.17.9
|
||||
'@babel/generator': 7.17.9
|
||||
|
@ -1521,6 +1523,33 @@ importers:
|
|||
'@types/unist': 2.0.6
|
||||
astro-scripts: link:../../../scripts
|
||||
|
||||
packages/telemetry:
|
||||
specifiers:
|
||||
'@types/dlv': ^1.1.2
|
||||
'@types/node': ^14.18.13
|
||||
astro-scripts: workspace:*
|
||||
ci-info: ^3.3.0
|
||||
debug: ^4.3.4
|
||||
dlv: ^1.1.3
|
||||
dset: ^3.1.1
|
||||
escalade: ^3.1.1
|
||||
is-docker: ^3.0.0
|
||||
is-wsl: ^2.2.0
|
||||
node-fetch: ^3.2.3
|
||||
dependencies:
|
||||
ci-info: 3.3.0
|
||||
debug: 4.3.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.1
|
||||
escalade: 3.1.1
|
||||
is-docker: 3.0.0
|
||||
is-wsl: 2.2.0
|
||||
node-fetch: 3.2.3
|
||||
devDependencies:
|
||||
'@types/dlv': 1.1.2
|
||||
'@types/node': 14.18.13
|
||||
astro-scripts: link:../../scripts
|
||||
|
||||
packages/webapi:
|
||||
specifiers:
|
||||
'@rollup/plugin-alias': ^3.1.9
|
||||
|
@ -3881,6 +3910,10 @@ packages:
|
|||
resolution: {integrity: sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==}
|
||||
dev: true
|
||||
|
||||
/@types/dlv/1.1.2:
|
||||
resolution: {integrity: sha512-OyiZ3jEKu7RtGO1yp9oOdK0cTwZ/10oE9PDJ6fyN3r9T5wkyOcvr6awdugjYdqF6KVO5eUvt7jx7rk2Eylufow==}
|
||||
dev: true
|
||||
|
||||
/@types/estree-jsx/0.0.1:
|
||||
resolution: {integrity: sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A==}
|
||||
dependencies:
|
||||
|
@ -5293,7 +5326,6 @@ packages:
|
|||
/data-uri-to-buffer/4.0.0:
|
||||
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: true
|
||||
|
||||
/dataloader/1.4.0:
|
||||
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
|
||||
|
@ -5528,6 +5560,11 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/dset/3.1.1:
|
||||
resolution: {integrity: sha512-hYf+jZNNqJBD2GiMYb+5mqOIX4R4RRHXU3qWMWYN+rqcR2/YpRL2bUHr8C8fU+5DNvqYjJ8YvMGSLuVPWU1cNg==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/duplexer/0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
dev: true
|
||||
|
@ -6137,7 +6174,6 @@ packages:
|
|||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.2.1
|
||||
dev: true
|
||||
|
||||
/file-entry-cache/6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
|
@ -6215,7 +6251,6 @@ packages:
|
|||
engines: {node: '>=12.20.0'}
|
||||
dependencies:
|
||||
fetch-blob: 3.1.5
|
||||
dev: true
|
||||
|
||||
/fraction.js/4.2.0:
|
||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||
|
@ -6872,7 +6907,12 @@ packages:
|
|||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/is-docker/3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/is-extendable/0.1.1:
|
||||
resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=}
|
||||
|
@ -7061,7 +7101,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
dev: true
|
||||
|
||||
/isarray/0.0.1:
|
||||
resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
|
||||
|
@ -8057,7 +8096,6 @@ packages:
|
|||
/node-domexception/1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: true
|
||||
|
||||
/node-fetch/2.6.7:
|
||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
||||
|
@ -8077,7 +8115,6 @@ packages:
|
|||
data-uri-to-buffer: 4.0.0
|
||||
fetch-blob: 3.1.5
|
||||
formdata-polyfill: 4.0.10
|
||||
dev: true
|
||||
|
||||
/node-releases/2.0.3:
|
||||
resolution: {integrity: sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==}
|
||||
|
@ -10507,7 +10544,6 @@ packages:
|
|||
/web-streams-polyfill/3.2.1:
|
||||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/webidl-conversions/3.0.1:
|
||||
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
|
||||
|
|
|
@ -18,7 +18,12 @@ let config = await loadConfig({
|
|||
cwd: fileURLToPath(projDir),
|
||||
});
|
||||
|
||||
const server = await dev(config, { logging: { level: 'error' } });
|
||||
const telemetry = {
|
||||
record() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
const server = await dev(config, { logging: { level: 'error' }, telemetry });
|
||||
|
||||
// Prime the server so initial memory is created
|
||||
await fetch(`http://localhost:3000/page-0`);
|
||||
|
|
|
@ -47,6 +47,7 @@ async function run() {
|
|||
|
||||
try {
|
||||
await execa('pnpm', ['install', '--ignore-scripts', '--frozen-lockfile=false', isExternal ? '--shamefully-hoist' : ''].filter(x => x), { cwd: fileURLToPath(directory), stdio: 'inherit' });
|
||||
await execa('pnpm', ['astro', 'telemetry', 'disable']);
|
||||
await execa('pnpm', ['run', 'build'], { cwd: fileURLToPath(directory), stdio: 'inherit' });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
|
Loading…
Reference in a new issue