diff --git a/cli/eslint.config.js b/cli/eslint.config.js index e706c0059..94a7b3dcf 100644 --- a/cli/eslint.config.js +++ b/cli/eslint.config.js @@ -9,5 +9,17 @@ export default tseslint.config({ ignores: ["node_modules/**", "dist/**"], rules: { "no-undef": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], }, }); diff --git a/cli/package-lock.json b/cli/package-lock.json index 43436cd3b..e357abaa0 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -9,6 +9,7 @@ "dependencies": { "@inquirer/prompts": "^7.0.1", "@oclif/core": "^4", + "@scarf/scarf": "^1.4.0", "chalk": "^5.3.0", "chokidar": "^4.0.1", "gradient-string": "^3.0.0", @@ -2259,6 +2260,13 @@ "node": ">=12" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sindresorhus/is": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", diff --git a/cli/package.json b/cli/package.json index 66ebc148b..6bb4f33d9 100644 --- a/cli/package.json +++ b/cli/package.json @@ -31,6 +31,7 @@ "dependencies": { "@inquirer/prompts": "^7.0.1", "@oclif/core": "^4", + "@scarf/scarf": "^1.4.0", "chalk": "^5.3.0", "chokidar": "^4.0.1", "gradient-string": "^3.0.0", diff --git a/cli/src/commands/new/index.ts b/cli/src/commands/new/index.ts index 1e8c1f8be..85cc49d0b 100644 --- a/cli/src/commands/new/index.ts +++ b/cli/src/commands/new/index.ts @@ -15,6 +15,7 @@ import path from "node:path"; import * as fs from "../../util/fs.js"; import * as vi from "../../util/versioninfo.js"; +import * as http from "../../util/http.js"; import { execFile, exec } from "../../util/cp.js"; import { isOnline } from "../../util/index.js"; import { MinGoVersion, MinNodeVersion, MinTinyGoVersion, SDK, parseSDK } from "../../custom/globals.js"; @@ -30,6 +31,8 @@ import { isErrorWithName } from "../../util/errors.js"; const MODUS_DEFAULT_TEMPLATE_NAME = "default"; +const SCARF_ENDPOINT = "hypermode.gateway"; + export default class NewCommand extends BaseCommand { static description = "Create a new Modus app"; @@ -148,7 +151,8 @@ export default class NewCommand extends BaseCommand { } this.log(); - await this.createApp(name, dir, sdk, MODUS_DEFAULT_TEMPLATE_NAME, flags["no-prompt"], flags.prerelease, createGitRepo); + + await this.createApp(name, dir, sdk, MODUS_DEFAULT_TEMPLATE_NAME, flags.prerelease, createGitRepo); } catch (err) { if (isErrorWithName(err) && err.name === "ExitPromptError") { this.abort(); @@ -158,6 +162,22 @@ export default class NewCommand extends BaseCommand { } } + private async collectInstallInfo(sdk: string, sdkVersion: string) { + try { + // Skip metrics collection if environment variables are set + if (process.env.SCARF_NO_ANALYTICS !== "true" && process.env.DO_NOT_TRACK !== "true") { + const version = this.config.version; + const platform = os.platform(); + const arch = os.arch(); + const nodeVersion = process.version; + + await http.get(`https://${SCARF_ENDPOINT}.scarf.sh/${version}/${platform}/${arch}/${nodeVersion}/${sdk}/${sdkVersion}`); + } + } catch (_error) { + // Fail silently if an error occurs during the analytics call + } + } + private async validateSdkPrereq(sdk: string) { const sdkText = `Modus ${sdk} SDK`; switch (sdk) { @@ -225,7 +245,7 @@ export default class NewCommand extends BaseCommand { } } - private async createApp(name: string, dir: string, sdk: SDK, template: string, force: boolean, prerelease: boolean, createGitRepo: boolean) { + private async createApp(name: string, dir: string, sdk: SDK, template: string, prerelease: boolean, createGitRepo: boolean) { const sdkText = `Modus ${sdk} SDK`; // Verify and/or install the Modus SDK @@ -287,6 +307,8 @@ export default class NewCommand extends BaseCommand { // Create the app this.log(chalk.dim(`Using ${sdkText} ${sdkVersion}`)); + + await this.collectInstallInfo(sdk, sdkVersion); await withSpinner(`Creating a new Modus ${sdk} app.`, async () => { if (!(await fs.exists(dir))) { await fs.mkdir(dir, { recursive: true });