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:
|
test:
|
||||||
name: 'Test: ${{ matrix.os }} (node@${{ matrix.node_version }})'
|
name: 'Test: ${{ matrix.os }} (node@${{ matrix.node_version }})'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
ASTRO_TELEMETRY_DISABLED: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
"@astrojs/language-server": "^0.13.4",
|
"@astrojs/language-server": "^0.13.4",
|
||||||
"@astrojs/markdown-remark": "^0.9.2",
|
"@astrojs/markdown-remark": "^0.9.2",
|
||||||
"@astrojs/prism": "0.4.1",
|
"@astrojs/prism": "0.4.1",
|
||||||
|
"@astrojs/telemetry": "^0.0.1",
|
||||||
"@astrojs/webapi": "^0.11.1",
|
"@astrojs/webapi": "^0.11.1",
|
||||||
"@babel/core": "^7.17.9",
|
"@babel/core": "^7.17.9",
|
||||||
"@babel/generator": "^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 * as colors from 'kleur/colors';
|
||||||
import yargs from 'yargs-parser';
|
import yargs from 'yargs-parser';
|
||||||
import { z } from 'zod';
|
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 { nodeLogDestination, enableVerboseLogging } from '../core/logger/node.js';
|
||||||
import build from '../core/build/index.js';
|
import build from '../core/build/index.js';
|
||||||
import add from '../core/add/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 preview from '../core/preview/index.js';
|
||||||
import { check } from './check.js';
|
import { check } from './check.js';
|
||||||
import { openInBrowser } from './open.js';
|
import { openInBrowser } from './open.js';
|
||||||
|
import * as telemetryHandler from './telemetry.js';
|
||||||
import { loadConfig } from '../core/config.js';
|
import { loadConfig } from '../core/config.js';
|
||||||
import { printHelp, formatErrorMessage, formatConfigErrorMessage } from '../core/messages.js';
|
import { printHelp, formatErrorMessage, formatConfigErrorMessage } from '../core/messages.js';
|
||||||
import { createSafeError } from '../core/util.js';
|
import { createSafeError } from '../core/util.js';
|
||||||
|
@ -27,7 +31,8 @@ type CLICommand =
|
||||||
| 'build'
|
| 'build'
|
||||||
| 'preview'
|
| 'preview'
|
||||||
| 'reload'
|
| 'reload'
|
||||||
| 'check';
|
| 'check'
|
||||||
|
| 'telemetry';
|
||||||
|
|
||||||
/** Display --help flag */
|
/** Display --help flag */
|
||||||
function printAstroHelp() {
|
function printAstroHelp() {
|
||||||
|
@ -41,6 +46,7 @@ function printAstroHelp() {
|
||||||
['build', 'Build a pre-compiled production-ready site.'],
|
['build', 'Build a pre-compiled production-ready site.'],
|
||||||
['preview', 'Preview your build locally before deploying.'],
|
['preview', 'Preview your build locally before deploying.'],
|
||||||
['check', 'Check your project for errors.'],
|
['check', 'Check your project for errors.'],
|
||||||
|
['telemetry', 'Enable/disable anonymous data collection.'],
|
||||||
['--version', 'Show the version number and exit.'],
|
['--version', 'Show the version number and exit.'],
|
||||||
['--help', 'Show this help message.'],
|
['--help', 'Show this help message.'],
|
||||||
],
|
],
|
||||||
|
@ -67,6 +73,7 @@ async function printVersion() {
|
||||||
function resolveCommand(flags: Arguments): CLICommand {
|
function resolveCommand(flags: Arguments): CLICommand {
|
||||||
const cmd = flags._[2] as string;
|
const cmd = flags._[2] as string;
|
||||||
if (cmd === 'add') return 'add';
|
if (cmd === 'add') return 'add';
|
||||||
|
if (cmd === 'telemetry') return 'telemetry';
|
||||||
if (flags.version) return 'version';
|
if (flags.version) return 'version';
|
||||||
else if (flags.help) return 'help';
|
else if (flags.help) return 'help';
|
||||||
|
|
||||||
|
@ -103,12 +110,28 @@ export async function cli(args: string[]) {
|
||||||
} else if (flags.silent) {
|
} else if (flags.silent) {
|
||||||
logging.level = '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) {
|
switch (cmd) {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
try {
|
try {
|
||||||
const packages = flags._.slice(3) as string[];
|
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) {
|
} catch (err) {
|
||||||
return throwAndExit(err);
|
return throwAndExit(err);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +139,13 @@ export async function cli(args: string[]) {
|
||||||
case 'dev': {
|
case 'dev': {
|
||||||
try {
|
try {
|
||||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
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
|
return await new Promise(() => {}); // lives forever
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return throwAndExit(err);
|
return throwAndExit(err);
|
||||||
|
@ -126,7 +155,13 @@ export async function cli(args: string[]) {
|
||||||
case 'build': {
|
case 'build': {
|
||||||
try {
|
try {
|
||||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
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) {
|
} catch (err) {
|
||||||
return throwAndExit(err);
|
return throwAndExit(err);
|
||||||
}
|
}
|
||||||
|
@ -134,6 +169,12 @@ export async function cli(args: string[]) {
|
||||||
|
|
||||||
case 'check': {
|
case 'check': {
|
||||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
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);
|
const ret = await check(config);
|
||||||
return process.exit(ret);
|
return process.exit(ret);
|
||||||
}
|
}
|
||||||
|
@ -141,7 +182,13 @@ export async function cli(args: string[]) {
|
||||||
case 'preview': {
|
case 'preview': {
|
||||||
try {
|
try {
|
||||||
const config = await loadConfig({ cwd: root, flags, cmd });
|
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
|
return await server.closed(); // keep alive until the server is closed
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return throwAndExit(err);
|
return throwAndExit(err);
|
||||||
|
@ -150,6 +197,12 @@ export async function cli(args: string[]) {
|
||||||
|
|
||||||
case 'docs': {
|
case 'docs': {
|
||||||
try {
|
try {
|
||||||
|
await telemetry.record(
|
||||||
|
event.eventCliSession({
|
||||||
|
astroVersion: process.env.PACKAGE_VERSION ?? '',
|
||||||
|
cliCommand: 'docs',
|
||||||
|
})
|
||||||
|
);
|
||||||
return await openInBrowser('https://docs.astro.build/');
|
return await openInBrowser('https://docs.astro.build/');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return throwAndExit(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 yargs from 'yargs-parser';
|
||||||
|
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { existsSync, promises as fs } from 'fs';
|
import { existsSync, promises as fs } from 'fs';
|
||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
|
@ -24,6 +25,7 @@ import { appendForwardSlash } from '../path.js';
|
||||||
export interface AddOptions {
|
export interface AddOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
flags: yargs.Arguments;
|
flags: yargs.Arguments;
|
||||||
|
telemetry: AstroTelemetry;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ export interface IntegrationInfo {
|
||||||
dependencies: [name: string, version: string][];
|
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) {
|
if (flags.help) {
|
||||||
printHelp({
|
printHelp({
|
||||||
commandName: 'astro add',
|
commandName: 'astro add',
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { AstroConfig, BuildConfig, ManifestData } from '../../@types/astro';
|
import type { AstroConfig, BuildConfig, ManifestData } from '../../@types/astro';
|
||||||
import type { LogOptions } from '../logger/core';
|
import type { LogOptions } from '../logger/core';
|
||||||
|
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as colors from 'kleur/colors';
|
import * as colors from 'kleur/colors';
|
||||||
|
@ -33,13 +34,11 @@ import { fixViteErrorMessage } from '../errors.js';
|
||||||
export interface BuildOptions {
|
export interface BuildOptions {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
|
telemetry: AstroTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `astro build` */
|
/** `astro build` */
|
||||||
export default async function build(
|
export default async function build(config: AstroConfig, options: BuildOptions): Promise<void> {
|
||||||
config: AstroConfig,
|
|
||||||
options: BuildOptions = { logging: nodeLogOptions }
|
|
||||||
): Promise<void> {
|
|
||||||
applyPolyfill();
|
applyPolyfill();
|
||||||
const builder = new AstroBuilder(config, options);
|
const builder = new AstroBuilder(config, options);
|
||||||
await builder.run();
|
await builder.run();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { AddressInfo } from 'net';
|
import type { AddressInfo } from 'net';
|
||||||
|
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import * as vite from 'vite';
|
import * as vite from 'vite';
|
||||||
import type { AstroConfig } from '../../@types/astro';
|
import type { AstroConfig } from '../../@types/astro';
|
||||||
|
@ -17,6 +18,7 @@ import { apply as applyPolyfill } from '../polyfill.js';
|
||||||
|
|
||||||
export interface DevOptions {
|
export interface DevOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
|
telemetry: AstroTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DevServer {
|
export interface DevServer {
|
||||||
|
@ -25,12 +27,10 @@ export interface DevServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `astro dev` */
|
/** `astro dev` */
|
||||||
export default async function dev(
|
export default async function dev(config: AstroConfig, options: DevOptions): Promise<DevServer> {
|
||||||
config: AstroConfig,
|
|
||||||
options: DevOptions = { logging: nodeLogOptions }
|
|
||||||
): Promise<DevServer> {
|
|
||||||
const devStart = performance.now();
|
const devStart = performance.now();
|
||||||
applyPolyfill();
|
applyPolyfill();
|
||||||
|
await options.telemetry.record([]);
|
||||||
config = await runHookConfigSetup({ config, command: 'dev' });
|
config = await runHookConfigSetup({ config, command: 'dev' });
|
||||||
const { host, port } = config.server;
|
const { host, port } = config.server;
|
||||||
const viteConfig = await createVite(
|
const viteConfig = await createVite(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/**
|
import type { AddressInfo } from 'net';
|
||||||
* Dev server messages (organized here to prevent clutter)
|
import type { AstroConfig } from '../@types/astro';
|
||||||
*/
|
import os from 'os';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bold,
|
bold,
|
||||||
dim,
|
dim,
|
||||||
|
@ -15,10 +14,9 @@ import {
|
||||||
black,
|
black,
|
||||||
bgRed,
|
bgRed,
|
||||||
bgWhite,
|
bgWhite,
|
||||||
|
bgCyan,
|
||||||
} from 'kleur/colors';
|
} from 'kleur/colors';
|
||||||
import os from 'os';
|
import boxen from 'boxen';
|
||||||
import type { AddressInfo } from 'net';
|
|
||||||
import type { AstroConfig } from '../@types/astro';
|
|
||||||
import { collectErrorMetadata, cleanErrorStack } from './errors.js';
|
import { collectErrorMetadata, cleanErrorStack } from './errors.js';
|
||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
import { emoji, getLocalAddress, padMultilineString } from './util.js';
|
import { emoji, getLocalAddress, padMultilineString } from './util.js';
|
||||||
|
@ -116,6 +114,37 @@ export function devStart({
|
||||||
return messages.map((msg) => ` ${msg}`).join('\n');
|
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 }) {
|
export function prerelease({ currentVersion }: { currentVersion: string }) {
|
||||||
const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '');
|
const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '');
|
||||||
const badge = bgYellow(black(` ${tag} `));
|
const badge = bgYellow(black(` ${tag} `));
|
||||||
|
@ -227,7 +256,7 @@ export function printHelp({
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
raw += `${opts.prefix}${bold(`${row[0]}`.padStart(opts.padding - opts.prefix.length))}`;
|
raw += `${opts.prefix}${bold(`${row[0]}`.padStart(opts.padding - opts.prefix.length))}`;
|
||||||
if (split) raw += '\n ';
|
if (split) raw += '\n ';
|
||||||
raw += dim(row[1]) + '\n';
|
raw += ' ' + dim(row[1]) + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
return raw.slice(0, -1); // remove latest \n
|
return raw.slice(0, -1); // remove latest \n
|
||||||
|
@ -252,7 +281,7 @@ export function printHelp({
|
||||||
message.push(
|
message.push(
|
||||||
linebreak(),
|
linebreak(),
|
||||||
title('Commands'),
|
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 { AstroConfig } from '../../@types/astro';
|
||||||
import type { LogOptions } from '../logger/core';
|
import type { LogOptions } from '../logger/core';
|
||||||
import type { AddressInfo } from 'net';
|
import type { AddressInfo } from 'net';
|
||||||
|
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||||
|
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import sirv from 'sirv';
|
import sirv from 'sirv';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
@ -12,6 +14,7 @@ import { getResolvedHostForHttpServer } from './util.js';
|
||||||
|
|
||||||
interface PreviewOptions {
|
interface PreviewOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
|
telemetry: AstroTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreviewServer {
|
export interface PreviewServer {
|
||||||
|
|
|
@ -86,10 +86,17 @@ export async function loadFixture(inlineConfig) {
|
||||||
level: 'error',
|
level: 'error',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @type {import('@astrojs/telemetry').AstroTelemetry} */
|
||||||
|
const telemetry = {
|
||||||
|
record() {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
build: (opts = {}) => build(config, { mode: 'development', logging, ...opts }),
|
build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }),
|
||||||
startDevServer: async (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
|
config.server.port = devResult.address.port; // update port
|
||||||
return devResult;
|
return devResult;
|
||||||
},
|
},
|
||||||
|
@ -97,7 +104,7 @@ export async function loadFixture(inlineConfig) {
|
||||||
fetch: (url, init) =>
|
fetch: (url, init) =>
|
||||||
fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init),
|
fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init),
|
||||||
preview: async (opts = {}) => {
|
preview: async (opts = {}) => {
|
||||||
const previewServer = await preview(config, { logging, ...opts });
|
const previewServer = await preview(config, { logging, telemetry, ...opts });
|
||||||
return previewServer;
|
return previewServer;
|
||||||
},
|
},
|
||||||
readFile: (filePath) =>
|
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/language-server': ^0.13.4
|
||||||
'@astrojs/markdown-remark': ^0.9.2
|
'@astrojs/markdown-remark': ^0.9.2
|
||||||
'@astrojs/prism': 0.4.1
|
'@astrojs/prism': 0.4.1
|
||||||
|
'@astrojs/telemetry': ^0.0.1
|
||||||
'@astrojs/webapi': ^0.11.1
|
'@astrojs/webapi': ^0.11.1
|
||||||
'@babel/core': ^7.17.9
|
'@babel/core': ^7.17.9
|
||||||
'@babel/generator': ^7.17.9
|
'@babel/generator': ^7.17.9
|
||||||
|
@ -546,6 +547,7 @@ importers:
|
||||||
'@astrojs/language-server': 0.13.4
|
'@astrojs/language-server': 0.13.4
|
||||||
'@astrojs/markdown-remark': link:../markdown/remark
|
'@astrojs/markdown-remark': link:../markdown/remark
|
||||||
'@astrojs/prism': link:../astro-prism
|
'@astrojs/prism': link:../astro-prism
|
||||||
|
'@astrojs/telemetry': link:../telemetry
|
||||||
'@astrojs/webapi': link:../webapi
|
'@astrojs/webapi': link:../webapi
|
||||||
'@babel/core': 7.17.9
|
'@babel/core': 7.17.9
|
||||||
'@babel/generator': 7.17.9
|
'@babel/generator': 7.17.9
|
||||||
|
@ -1521,6 +1523,33 @@ importers:
|
||||||
'@types/unist': 2.0.6
|
'@types/unist': 2.0.6
|
||||||
astro-scripts: link:../../../scripts
|
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:
|
packages/webapi:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@rollup/plugin-alias': ^3.1.9
|
'@rollup/plugin-alias': ^3.1.9
|
||||||
|
@ -3881,6 +3910,10 @@ packages:
|
||||||
resolution: {integrity: sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==}
|
resolution: {integrity: sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/dlv/1.1.2:
|
||||||
|
resolution: {integrity: sha512-OyiZ3jEKu7RtGO1yp9oOdK0cTwZ/10oE9PDJ6fyN3r9T5wkyOcvr6awdugjYdqF6KVO5eUvt7jx7rk2Eylufow==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/estree-jsx/0.0.1:
|
/@types/estree-jsx/0.0.1:
|
||||||
resolution: {integrity: sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A==}
|
resolution: {integrity: sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5293,7 +5326,6 @@ packages:
|
||||||
/data-uri-to-buffer/4.0.0:
|
/data-uri-to-buffer/4.0.0:
|
||||||
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
|
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/dataloader/1.4.0:
|
/dataloader/1.4.0:
|
||||||
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
|
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
|
||||||
|
@ -5528,6 +5560,11 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/dset/3.1.1:
|
||||||
|
resolution: {integrity: sha512-hYf+jZNNqJBD2GiMYb+5mqOIX4R4RRHXU3qWMWYN+rqcR2/YpRL2bUHr8C8fU+5DNvqYjJ8YvMGSLuVPWU1cNg==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/duplexer/0.1.2:
|
/duplexer/0.1.2:
|
||||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -6137,7 +6174,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-domexception: 1.0.0
|
node-domexception: 1.0.0
|
||||||
web-streams-polyfill: 3.2.1
|
web-streams-polyfill: 3.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/file-entry-cache/6.0.1:
|
/file-entry-cache/6.0.1:
|
||||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||||
|
@ -6215,7 +6251,6 @@ packages:
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
fetch-blob: 3.1.5
|
fetch-blob: 3.1.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
/fraction.js/4.2.0:
|
/fraction.js/4.2.0:
|
||||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||||
|
@ -6872,7 +6907,12 @@ packages:
|
||||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
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:
|
/is-extendable/0.1.1:
|
||||||
resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=}
|
resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=}
|
||||||
|
@ -7061,7 +7101,6 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/isarray/0.0.1:
|
/isarray/0.0.1:
|
||||||
resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
|
resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
|
||||||
|
@ -8057,7 +8096,6 @@ packages:
|
||||||
/node-domexception/1.0.0:
|
/node-domexception/1.0.0:
|
||||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||||
engines: {node: '>=10.5.0'}
|
engines: {node: '>=10.5.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/node-fetch/2.6.7:
|
/node-fetch/2.6.7:
|
||||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
||||||
|
@ -8077,7 +8115,6 @@ packages:
|
||||||
data-uri-to-buffer: 4.0.0
|
data-uri-to-buffer: 4.0.0
|
||||||
fetch-blob: 3.1.5
|
fetch-blob: 3.1.5
|
||||||
formdata-polyfill: 4.0.10
|
formdata-polyfill: 4.0.10
|
||||||
dev: true
|
|
||||||
|
|
||||||
/node-releases/2.0.3:
|
/node-releases/2.0.3:
|
||||||
resolution: {integrity: sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==}
|
resolution: {integrity: sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==}
|
||||||
|
@ -10507,7 +10544,6 @@ packages:
|
||||||
/web-streams-polyfill/3.2.1:
|
/web-streams-polyfill/3.2.1:
|
||||||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/webidl-conversions/3.0.1:
|
/webidl-conversions/3.0.1:
|
||||||
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
|
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
|
||||||
|
|
|
@ -18,7 +18,12 @@ let config = await loadConfig({
|
||||||
cwd: fileURLToPath(projDir),
|
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
|
// Prime the server so initial memory is created
|
||||||
await fetch(`http://localhost:3000/page-0`);
|
await fetch(`http://localhost:3000/page-0`);
|
||||||
|
|
|
@ -47,6 +47,7 @@ async function run() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await execa('pnpm', ['install', '--ignore-scripts', '--frozen-lockfile=false', isExternal ? '--shamefully-hoist' : ''].filter(x => x), { cwd: fileURLToPath(directory), stdio: 'inherit' });
|
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' });
|
await execa('pnpm', ['run', 'build'], { cwd: fileURLToPath(directory), stdio: 'inherit' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
Loading…
Reference in a new issue