@grada-run/grada 0.0.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy-docs.yml +37 -0
- package/.github/workflows/e2e.yml +73 -0
- package/.github/workflows/iac-validation.yml +303 -0
- package/.github/workflows/publish.yml +68 -0
- package/.github/workflows/sync-bedrock-models.yml +57 -0
- package/.github/workflows/test.yml +43 -0
- package/.muserules +31 -0
- package/LICENSE +21 -0
- package/README.md +193 -3
- package/apps/docs/.astro/collections/docs.schema.json +644 -0
- package/apps/docs/.astro/content-assets.mjs +4 -0
- package/apps/docs/.astro/content-modules.mjs +4 -0
- package/apps/docs/.astro/content.d.ts +179 -0
- package/apps/docs/.astro/data-store.json +1 -0
- package/apps/docs/.astro/dev.json +14 -0
- package/apps/docs/.astro/settings.json +5 -0
- package/apps/docs/.astro/types.d.ts +2 -0
- package/apps/docs/astro.config.mjs +97 -0
- package/apps/docs/package.json +17 -0
- package/apps/docs/src/content/docs/adrs/0001-s3-native-state-locking.md +37 -0
- package/apps/docs/src/content/docs/adrs/0002-eject-mechanism-pure-iac.md +39 -0
- package/apps/docs/src/content/docs/adrs/0003-sync-ai-context-strategy.md +48 -0
- package/apps/docs/src/content/docs/adrs/0004-iac-driven-diagnostic-context.md +37 -0
- package/apps/docs/src/content/docs/adrs/0005-ecs-fargate-alb-runtime-target.md +38 -0
- package/apps/docs/src/content/docs/adrs/0006-github-oidc-no-stored-keys.md +37 -0
- package/apps/docs/src/content/docs/adrs/0007-framework-detection-with-fallback.md +37 -0
- package/apps/docs/src/content/docs/adrs/0008-secrets-names-in-git-values-in-aws.md +37 -0
- package/apps/docs/src/content/docs/adrs/0009-regenerate-with-backup-on-rerun.md +37 -0
- package/apps/docs/src/content/docs/adrs/0010-advisory-only-security-scans.md +37 -0
- package/apps/docs/src/content/docs/cli/add.md +85 -0
- package/apps/docs/src/content/docs/cli/apply.md +35 -0
- package/apps/docs/src/content/docs/cli/db.md +200 -0
- package/apps/docs/src/content/docs/cli/destroy.md +35 -0
- package/apps/docs/src/content/docs/cli/diagnose.md +37 -0
- package/apps/docs/src/content/docs/cli/doctor.md +28 -0
- package/apps/docs/src/content/docs/cli/domain.md +57 -0
- package/apps/docs/src/content/docs/cli/drift.md +40 -0
- package/apps/docs/src/content/docs/cli/eject.md +33 -0
- package/apps/docs/src/content/docs/cli/exec.md +49 -0
- package/apps/docs/src/content/docs/cli/gc.md +37 -0
- package/apps/docs/src/content/docs/cli/init.md +72 -0
- package/apps/docs/src/content/docs/cli/logs.md +39 -0
- package/apps/docs/src/content/docs/cli/rollback.md +51 -0
- package/apps/docs/src/content/docs/cli/secrets.md +73 -0
- package/apps/docs/src/content/docs/cli/sleep.md +53 -0
- package/apps/docs/src/content/docs/cli/status.md +34 -0
- package/apps/docs/src/content/docs/cli/sync-ai.md +27 -0
- package/apps/docs/src/content/docs/guides/architecture.md +87 -0
- package/apps/docs/src/content/docs/guides/aws-credentials.md +72 -0
- package/apps/docs/src/content/docs/guides/background-workers.md +45 -0
- package/apps/docs/src/content/docs/guides/cicd-pipeline.md +64 -0
- package/apps/docs/src/content/docs/guides/database-connections.md +64 -0
- package/apps/docs/src/content/docs/guides/docker-compose.md +37 -0
- package/apps/docs/src/content/docs/guides/dockerfiles.md +46 -0
- package/apps/docs/src/content/docs/guides/ephemeral-pr-previews.md +39 -0
- package/apps/docs/src/content/docs/guides/examples.md +50 -0
- package/apps/docs/src/content/docs/guides/frameworks.md +88 -0
- package/apps/docs/src/content/docs/guides/headless.md +87 -0
- package/apps/docs/src/content/docs/guides/quickstart.md +52 -0
- package/apps/docs/src/content/docs/guides/rerun-init.md +43 -0
- package/apps/docs/src/content/docs/guides/secrets-management.md +83 -0
- package/apps/docs/src/content/docs/guides/understanding-your-bill.md +63 -0
- package/apps/docs/src/content/docs/index.mdx +103 -0
- package/apps/docs/src/content/docs/migrations/astro-vercel-to-aws.md +55 -0
- package/apps/docs/src/content/docs/migrations/heroku-procfile-to-aws.md +41 -0
- package/apps/docs/src/content/docs/migrations/nextjs-vercel-to-aws.md +51 -0
- package/apps/docs/src/content/docs/migrations/sveltekit-vercel-to-aws.md +63 -0
- package/apps/docs/src/content/docs/roadmap.md +99 -0
- package/apps/docs/src/content/docs/testing-strategy.md +37 -0
- package/apps/docs/src/content.config.ts +7 -0
- package/apps/docs/src/custom.css +14 -0
- package/apps/docs/tsconfig.json +6 -0
- package/bin/cli.js +140 -0
- package/package.json +107 -7
- package/scripts/sync-bedrock-models.js +22 -0
- package/scripts/test-iac.js +261 -0
- package/specs/add-redis-sqs-bedrock.md +128 -0
- package/specs/add-storage-dynamodb.md +106 -0
- package/specs/bedrock-model-catalog.md +131 -0
- package/specs/ci-pipeline.md +17 -0
- package/specs/cost-transparency.md +115 -0
- package/specs/custom-domains-and-ses.md +153 -0
- package/specs/database-suite-expansion.md +151 -0
- package/specs/db-connect.md +69 -0
- package/specs/db-lifecycle-migrations.md +159 -0
- package/specs/dependency-aware-init.md +176 -0
- package/specs/deployment-safety.md +170 -0
- package/specs/diagnose.md +16 -0
- package/specs/docs-hub.md +16 -0
- package/specs/dx-polish.md +46 -0
- package/specs/e2e-testing.md +50 -0
- package/specs/exec.md +25 -0
- package/specs/finops-cron-drift.md +161 -0
- package/specs/gc.md +26 -0
- package/specs/integration-suite.md +16 -0
- package/specs/logs.md +32 -0
- package/specs/rollback-live-polling.md +40 -0
- package/specs/secrets-pull-audit.md +51 -0
- package/specs/serverless-lambda-target.md +133 -0
- package/specs/status.md +31 -0
- package/specs/telemetry-and-spawn-hardening.md +69 -0
- package/specs/telemetry-hardening.md +35 -0
- package/specs/test-suite-deduplication.md +42 -0
- package/src/commands/add.js +1111 -0
- package/src/commands/apply.js +214 -0
- package/src/commands/db/backup.js +229 -0
- package/src/commands/db/connect.js +304 -0
- package/src/commands/db/enable-vector.js +344 -0
- package/src/commands/db/import.js +604 -0
- package/src/commands/db/migrate.js +477 -0
- package/src/commands/db/restore.js +361 -0
- package/src/commands/db.js +87 -0
- package/src/commands/destroy.js +217 -0
- package/src/commands/diagnose.js +460 -0
- package/src/commands/doctor.js +109 -0
- package/src/commands/domain.js +685 -0
- package/src/commands/drift.js +243 -0
- package/src/commands/eject.js +130 -0
- package/src/commands/exec.js +222 -0
- package/src/commands/gc.js +250 -0
- package/src/commands/init.js +649 -0
- package/src/commands/logs.js +256 -0
- package/src/commands/rollback.js +323 -0
- package/src/commands/secrets.js +485 -0
- package/src/commands/sleep.js +347 -0
- package/src/commands/status.js +309 -0
- package/src/commands/sync-ai.js +115 -0
- package/src/commands/wake.js +337 -0
- package/src/core/parser.js +128 -0
- package/src/core/telemetry.js +244 -0
- package/src/data/bedrock-models.json +896 -0
- package/src/utils/addons.js +126 -0
- package/src/utils/ai-rules.js +59 -0
- package/src/utils/args.js +91 -0
- package/src/utils/aws.js +178 -0
- package/src/utils/backup.js +69 -0
- package/src/utils/bedrock-catalog.js +511 -0
- package/src/utils/capabilities.js +500 -0
- package/src/utils/command.js +78 -0
- package/src/utils/db-tunnel.js +164 -0
- package/src/utils/detector.js +298 -0
- package/src/utils/dockerCompose.js +65 -0
- package/src/utils/domains.js +73 -0
- package/src/utils/ecs-runner.js +289 -0
- package/src/utils/ecs.js +92 -0
- package/src/utils/frameworks.js +55 -0
- package/src/utils/generator.js +527 -0
- package/src/utils/hcl.js +426 -0
- package/src/utils/lambda-ecr.js +185 -0
- package/src/utils/prompts.js +278 -0
- package/src/utils/rds.js +131 -0
- package/src/utils/resolvers.js +174 -0
- package/src/utils/sleep-state.js +140 -0
- package/src/utils/sleep-targets.js +139 -0
- package/src/utils/system.js +42 -0
- package/src/utils/terraform.js +70 -0
- package/src/utils/visualizer.js +381 -0
- package/src/utils/warnings.js +49 -0
- package/templates/README.md +150 -0
- package/templates/docker/django.Dockerfile +40 -0
- package/templates/docker/go.Dockerfile +23 -0
- package/templates/docker/nestjs.Dockerfile +33 -0
- package/templates/docker/nextjs.Dockerfile +55 -0
- package/templates/docker/node.Dockerfile +24 -0
- package/templates/docker/nuxt.Dockerfile +47 -0
- package/templates/docker/python.Dockerfile +38 -0
- package/templates/docker/rails.Dockerfile +59 -0
- package/templates/docker/static.Dockerfile +32 -0
- package/templates/docker/svelte.Dockerfile +52 -0
- package/templates/github/deploy-lambda.yml +120 -0
- package/templates/github/deploy.yml +138 -0
- package/templates/github/drift.yml +112 -0
- package/templates/github/preview-lambda.yml +86 -0
- package/templates/github/preview.yml +69 -0
- package/templates/github/teardown.yml +43 -0
- package/templates/terraform/addons/bedrock.tf +34 -0
- package/templates/terraform/addons/cron-lambda.tf +78 -0
- package/templates/terraform/addons/cron.tf +101 -0
- package/templates/terraform/addons/dynamodb.tf +73 -0
- package/templates/terraform/addons/redis.tf +64 -0
- package/templates/terraform/addons/s3.tf +143 -0
- package/templates/terraform/addons/ses.tf +73 -0
- package/templates/terraform/addons/sqs.tf +67 -0
- package/templates/terraform/backend.tf +22 -0
- package/templates/terraform/cloudfront-lambda.tf +80 -0
- package/templates/terraform/cloudfront.tf +80 -0
- package/templates/terraform/database-aurora-postgresql.tf +92 -0
- package/templates/terraform/database-mysql.tf +72 -0
- package/templates/terraform/database.tf +71 -0
- package/templates/terraform/main-lambda.tf +229 -0
- package/templates/terraform/main.tf +296 -0
- package/templates/terraform/network.tf +95 -0
- package/templates/terraform/oidc.tf +64 -0
- package/templates/terraform/secrets.tf +31 -0
- package/templates/terraform/worker.tf +69 -0
- package/tests/__snapshots__/generator.test.js.snap +9633 -0
- package/tests/add.test.js +2018 -0
- package/tests/ai.test.js +91 -0
- package/tests/apply.test.js +446 -0
- package/tests/args.test.js +86 -0
- package/tests/aws.test.js +244 -0
- package/tests/capabilities.test.js +304 -0
- package/tests/cli.test.js +29 -0
- package/tests/command.test.js +99 -0
- package/tests/commands-import.test.js +74 -0
- package/tests/db.test.js +2673 -0
- package/tests/destroy.test.js +365 -0
- package/tests/detector.test.js +79 -0
- package/tests/diagnose.test.js +765 -0
- package/tests/doctor.test.js +195 -0
- package/tests/domain.test.js +882 -0
- package/tests/drift.test.js +228 -0
- package/tests/e2e/helpers.js +122 -0
- package/tests/e2e/tier0.e2e.test.js +142 -0
- package/tests/e2e/tier1.live.e2e.test.js +118 -0
- package/tests/ecs.test.js +130 -0
- package/tests/eject.test.js +65 -0
- package/tests/exec.test.js +366 -0
- package/tests/gc.test.js +466 -0
- package/tests/generator.test.js +794 -0
- package/tests/headless.test.js +491 -0
- package/tests/helpers/clack.js +103 -0
- package/tests/helpers/console.js +41 -0
- package/tests/helpers/telemetry.js +37 -0
- package/tests/helpers/tmpdir.js +36 -0
- package/tests/lambda-ecr.test.js +185 -0
- package/tests/logs.test.js +436 -0
- package/tests/parser.test.js +160 -0
- package/tests/rds.test.js +244 -0
- package/tests/resolvers.test.js +279 -0
- package/tests/rollback.test.js +670 -0
- package/tests/secrets.test.js +729 -0
- package/tests/sleep-wake.test.js +998 -0
- package/tests/status.test.js +356 -0
- package/tests/system.test.js +70 -0
- package/tests/telemetry.test.js +520 -0
- package/tests/terraform.test.js +84 -0
- package/tests/visualizer.test.js +480 -0
- package/vitest.config.js +10 -0
- package/vitest.e2e.tier0.config.js +8 -0
- package/vitest.e2e.tier1.config.js +8 -0
- package/index.js +0 -2
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const TELEMETRY_ENDPOINT = 'https://eu.i.posthog.com/capture/';
|
|
7
|
+
const POSTHOG_API_KEY = 'phc_o2wgA3jVT9rVDiGSDzFAR42zZeiVGhhCY53HXVHUcYGT';
|
|
8
|
+
const pendingRequests = [];
|
|
9
|
+
|
|
10
|
+
const CLI_ENTRY_BASENAMES = ['cli.js', 'grada', 'grada-run', 'deploy-stack'];
|
|
11
|
+
|
|
12
|
+
let cachedDistinctId = null;
|
|
13
|
+
|
|
14
|
+
export function resetTelemetryIdentityCache() {
|
|
15
|
+
cachedDistinctId = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sha16(raw) {
|
|
19
|
+
return crypto.createHash('sha256').update(String(raw)).digest('hex').substring(0, 16);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// An env var counts as active only when defined with a meaningful value.
|
|
23
|
+
// This keeps CI='false', CI='0', and CI='' from masquerading as CI.
|
|
24
|
+
export function isActiveEnvValue(value) {
|
|
25
|
+
if (value === undefined || value === null) return false;
|
|
26
|
+
const normalized = String(value).trim().toLowerCase();
|
|
27
|
+
return normalized !== '' && normalized !== '0' && normalized !== 'false';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Distinguishes real CI pipelines from local shells/agents that set CI=true.
|
|
31
|
+
// Precedence: specific providers first, then generic CI, then none.
|
|
32
|
+
export function detectCiProvider(env = process.env) {
|
|
33
|
+
if (isActiveEnvValue(env.GITHUB_ACTIONS)) return 'github_actions';
|
|
34
|
+
if (isActiveEnvValue(env.GITLAB_CI)) return 'gitlab_ci';
|
|
35
|
+
if (isActiveEnvValue(env.CIRCLECI)) return 'circleci';
|
|
36
|
+
if (isActiveEnvValue(env.JENKINS_URL)) return 'jenkins';
|
|
37
|
+
if (isActiveEnvValue(env.CI) || isActiveEnvValue(env.CONTINUOUS_INTEGRATION)) return 'generic_ci';
|
|
38
|
+
return 'none';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isTestEnv(env = process.env) {
|
|
42
|
+
return Boolean(env.VITEST || env.NODE_ENV === 'test');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function defaultTelemetryIdPath() {
|
|
46
|
+
return path.join(os.homedir(), '.grada', 'telemetry-id');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function legacyTelemetryIdPath() {
|
|
50
|
+
return path.join(os.homedir(), '.deploy-stack', 'telemetry-id');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Migrates a legacy telemetry identity forward: when the new path is
|
|
54
|
+
// missing but the legacy one holds an identity, its contents seed the
|
|
55
|
+
// new file so the stable distinct ID survives the rebrand. Returns true
|
|
56
|
+
// when a migration happened. Never throws.
|
|
57
|
+
export function migrateLegacyTelemetryId(newPath, legacyPath) {
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(newPath)) return false;
|
|
60
|
+
const raw = fs.readFileSync(legacyPath, 'utf-8').trim();
|
|
61
|
+
if (!raw) return false;
|
|
62
|
+
fs.mkdirSync(path.dirname(newPath), { recursive: true });
|
|
63
|
+
fs.writeFileSync(newPath, `${raw}\n`, 'utf-8');
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Branch A: deterministic machine/CI fingerprint for ephemeral runners.
|
|
71
|
+
// Cwd is deliberately excluded so commands from different directories on
|
|
72
|
+
// the same runner share one Person within a run.
|
|
73
|
+
function fingerprintDistinctId() {
|
|
74
|
+
return sha16([
|
|
75
|
+
os.hostname(),
|
|
76
|
+
os.platform(),
|
|
77
|
+
os.arch(),
|
|
78
|
+
process.env.GITHUB_REPOSITORY || '',
|
|
79
|
+
process.env.GITHUB_RUN_ID || '',
|
|
80
|
+
process.env.GITLAB_PROJECT_PATH || '',
|
|
81
|
+
].join(':'));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Branch B: persistent random UUID, created on first use. Any filesystem
|
|
85
|
+
// failure falls back to the Branch A fingerprint so telemetry never throws.
|
|
86
|
+
function persistentDistinctId(idPath) {
|
|
87
|
+
try {
|
|
88
|
+
let raw = '';
|
|
89
|
+
try {
|
|
90
|
+
raw = fs.readFileSync(idPath, 'utf-8').trim();
|
|
91
|
+
} catch {
|
|
92
|
+
raw = '';
|
|
93
|
+
}
|
|
94
|
+
if (!raw) {
|
|
95
|
+
raw = crypto.randomUUID();
|
|
96
|
+
fs.mkdirSync(path.dirname(idPath), { recursive: true });
|
|
97
|
+
fs.writeFileSync(idPath, `${raw}\n`, 'utf-8');
|
|
98
|
+
}
|
|
99
|
+
return sha16(raw);
|
|
100
|
+
} catch {
|
|
101
|
+
return fingerprintDistinctId();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function resolveDistinctId({ ciProvider = detectCiProvider(), testEnv = isTestEnv() } = {}) {
|
|
106
|
+
if (cachedDistinctId) return cachedDistinctId;
|
|
107
|
+
const overridePath = process.env.GRADA_TELEMETRY_ID_PATH || process.env.DEPLOY_STACK_TELEMETRY_ID_PATH;
|
|
108
|
+
let resolved;
|
|
109
|
+
if (typeof overridePath === 'string' && overridePath.trim() !== '') {
|
|
110
|
+
resolved = persistentDistinctId(overridePath);
|
|
111
|
+
} else if (ciProvider !== 'none' || testEnv) {
|
|
112
|
+
resolved = fingerprintDistinctId();
|
|
113
|
+
} else {
|
|
114
|
+
const idPath = defaultTelemetryIdPath();
|
|
115
|
+
migrateLegacyTelemetryId(idPath, legacyTelemetryIdPath());
|
|
116
|
+
resolved = persistentDistinctId(idPath);
|
|
117
|
+
}
|
|
118
|
+
cachedDistinctId = resolved;
|
|
119
|
+
return resolved;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function trackEvent(eventName, properties) {
|
|
123
|
+
// 1. Respect privacy standards
|
|
124
|
+
if (process.env.DO_NOT_TRACK === '1' || process.env.DO_NOT_TRACK === 'true') {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 2. Keep all events: drop only missing, blank, or non-serializable
|
|
129
|
+
// (object/function) names; normalize the rest so strings like
|
|
130
|
+
// 'cli-error' plus booleans and numbers serialize safely.
|
|
131
|
+
if (
|
|
132
|
+
eventName === undefined ||
|
|
133
|
+
eventName === null ||
|
|
134
|
+
typeof eventName === 'object' ||
|
|
135
|
+
typeof eventName === 'function' ||
|
|
136
|
+
String(eventName).trim() === ''
|
|
137
|
+
) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const normalizedEvent = String(eventName);
|
|
141
|
+
|
|
142
|
+
// 3. Protect the PostHog column schema: only spread plain objects.
|
|
143
|
+
// Primitives and arrays are wrapped so e.g. a string never spreads its
|
|
144
|
+
// character indices (0, 1, 2, ...) as top-level columns.
|
|
145
|
+
let eventProps;
|
|
146
|
+
if (properties === undefined || properties === null) {
|
|
147
|
+
eventProps = {};
|
|
148
|
+
} else if (typeof properties === 'object' && !Array.isArray(properties)) {
|
|
149
|
+
eventProps = { ...properties };
|
|
150
|
+
} else {
|
|
151
|
+
eventProps = { raw_properties: properties };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 4. Resolve context first: provider before is_ci, entry before command.
|
|
155
|
+
const ciProvider = detectCiProvider();
|
|
156
|
+
const testEnv = isTestEnv();
|
|
157
|
+
const isCi = isActiveEnvValue(process.env.CI) || ciProvider !== 'none';
|
|
158
|
+
const isCliEntry = CLI_ENTRY_BASENAMES.includes(path.basename(process.argv?.[1] || ''));
|
|
159
|
+
|
|
160
|
+
// 5. Machine-scoped Person identity, stable across commands in this env.
|
|
161
|
+
const distinctId = resolveDistinctId({ ciProvider, testEnv });
|
|
162
|
+
|
|
163
|
+
// 6. Project-scoped grouping stays in the payload as a hash. Coerce
|
|
164
|
+
// first so non-string projectName values can never throw inside
|
|
165
|
+
// createHash; fall back to the working directory name when omitted.
|
|
166
|
+
let rawProjectName = eventProps.projectName;
|
|
167
|
+
if (rawProjectName === undefined || rawProjectName === null || rawProjectName === '') {
|
|
168
|
+
rawProjectName = path.basename(process.cwd()) || 'unknown';
|
|
169
|
+
}
|
|
170
|
+
const projectId = sha16(rawProjectName);
|
|
171
|
+
|
|
172
|
+
// 7. Strip the raw name out of the payload
|
|
173
|
+
delete eventProps.projectName;
|
|
174
|
+
|
|
175
|
+
const payload = {
|
|
176
|
+
api_key: POSTHOG_API_KEY,
|
|
177
|
+
event: normalizedEvent,
|
|
178
|
+
distinct_id: distinctId,
|
|
179
|
+
properties: {
|
|
180
|
+
os: process.platform,
|
|
181
|
+
node_version: process.version,
|
|
182
|
+
is_ci: isCi,
|
|
183
|
+
ci_provider: ciProvider,
|
|
184
|
+
is_test_env: testEnv,
|
|
185
|
+
is_tty: Boolean(process.stdout && process.stdout.isTTY),
|
|
186
|
+
is_cli_entry: isCliEntry,
|
|
187
|
+
cli_command: isCliEntry
|
|
188
|
+
? (process.env.CLI_COMMAND || process.argv.slice(2).join(' ') || 'unknown')
|
|
189
|
+
: 'module_import',
|
|
190
|
+
project_id: projectId,
|
|
191
|
+
framework: process.env.GRADA_FRAMEWORK || process.env.DEPLOY_STACK_FRAMEWORK || eventProps.framework || undefined,
|
|
192
|
+
...eventProps
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// 6. Fire and forget (No 'await' so we don't block the user's terminal)
|
|
197
|
+
const request = fetch(TELEMETRY_ENDPOINT, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: { 'Content-Type': 'application/json' },
|
|
200
|
+
body: JSON.stringify(payload),
|
|
201
|
+
}).catch(() => {
|
|
202
|
+
// Silently swallow network errors (e.g., user is offline)
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
pendingRequests.push(request);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function flushTelemetry() {
|
|
209
|
+
if (pendingRequests.length > 0) {
|
|
210
|
+
await Promise.all(pendingRequests);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Stamps the success flag onto wrapper properties using trackEvent's own
|
|
215
|
+
// normalization: plain objects merge, missing values send the flag alone,
|
|
216
|
+
// and anything else (strings, numbers, arrays) wraps as raw_properties so
|
|
217
|
+
// a primitive can never spread its indices as top-level columns.
|
|
218
|
+
function withSuccessFlag(properties, success) {
|
|
219
|
+
if (properties === undefined || properties === null) {
|
|
220
|
+
return { success };
|
|
221
|
+
}
|
|
222
|
+
if (typeof properties === 'object' && !Array.isArray(properties)) {
|
|
223
|
+
return { ...properties, success };
|
|
224
|
+
}
|
|
225
|
+
return { raw_properties: properties, success };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Reports a successful command outcome: tracks the event stamped
|
|
229
|
+
// `success: true` and flushes immediately, so a subsequent exit or
|
|
230
|
+
// long-lived process never loses it. Single definition for the
|
|
231
|
+
// track+flush pair every command repeats on its happy path.
|
|
232
|
+
export async function trackSuccess(eventName, properties = {}) {
|
|
233
|
+
trackEvent(eventName, withSuccessFlag(properties, true));
|
|
234
|
+
await flushTelemetry();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Reports a failed command outcome without terminating: tracks the event
|
|
238
|
+
// stamped `success: false` and flushes immediately. Companion to
|
|
239
|
+
// trackSuccess for catch blocks that must keep branching (auth recovery,
|
|
240
|
+
// not-found guidance) after reporting — failCommand covers terminal failures.
|
|
241
|
+
export async function trackFailure(eventName, properties = {}) {
|
|
242
|
+
trackEvent(eventName, withSuccessFlag(properties, false));
|
|
243
|
+
await flushTelemetry();
|
|
244
|
+
}
|