Skip to content

Commit 0ad3f2c

Browse files
authored
ref(script): Improve attribute creation script (#129)
1 parent 50c1b9b commit 0ad3f2c

File tree

3 files changed

+156
-33
lines changed

3 files changed

+156
-33
lines changed

package.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,13 @@
77
"lint": "biome check",
88
"fix": "biome check --write",
99
"test": "vitest",
10-
1110
"build": "yarn build:js && yarn build:python && yarn build:tarball",
12-
1311
"build:js": "(cd javascript/sentry-conventions && yarn build)",
14-
1512
"lint:python": "(cd python && uv run pyflakes src/ && uv run black src/ && uv run isort src/ && uv run mypy .)",
1613
"build:python": "(cd python && uv build)",
17-
1814
"build:tarball": "yarn build:tarball:js && yarn build:python && yarn build:tarball:model",
1915
"build:tarball:js": "(cd javascript/sentry-conventions && yarn build:tarball)",
2016
"build:tarball:model": "tsx scripts/gzip_folder.ts model model/sentry-conventions-json.tgz",
21-
2217
"create:attribute": "tsx scripts/create_attribute.ts",
2318
"generate": "tsx scripts/generate.ts"
2419
},
@@ -27,6 +22,7 @@
2722
},
2823
"devDependencies": {
2924
"@biomejs/biome": "1.9.4",
25+
"@clack/prompts": "^0.11.0",
3026
"@types/node": "^22.15.3",
3127
"@types/tar": "^6.1.13",
3228
"ajv": "^8.17.1",

scripts/create_attribute.ts

Lines changed: 133 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { execSync } from 'node:child_process';
22
import fs from 'node:fs';
33
import path from 'node:path';
4-
import readline from 'node:readline';
54
import { parseArgs } from 'node:util';
5+
import { confirm, intro, isCancel, log, outro, select, text } from '@clack/prompts';
66
import Ajv from 'ajv';
77

88
const HELP_TEXT = `
@@ -29,17 +29,6 @@ Examples:
2929
yarn run create:attribute --key http.route --description "The route pattern of the request" --type string --has_pii false --is_in_otel true --example "/users/:id" --alias "url.template"
3030
`;
3131

32-
const rl = readline.createInterface({
33-
input: process.stdin,
34-
output: process.stdout,
35-
});
36-
37-
const question = (query: string): Promise<string> => {
38-
return new Promise((resolve) => {
39-
rl.question(query, resolve);
40-
});
41-
};
42-
4332
const validateSchema = (data: unknown) => {
4433
const schema = JSON.parse(fs.readFileSync('schemas/attribute.schema.json', 'utf-8'));
4534
const ajv = new Ajv();
@@ -74,6 +63,8 @@ const createAttribute = async () => {
7463
process.exit(0);
7564
}
7665

66+
intro('Create new attribute');
67+
7768
// If any required option is provided, we'll use non-interactive mode
7869
const isInteractive = !(values.key || values.description || values.type || values.has_pii || values.is_in_otel);
7970

@@ -87,14 +78,14 @@ const createAttribute = async () => {
8778
let sdks: string | undefined;
8879

8980
if (isInteractive) {
90-
key = await question('Enter the attribute key (e.g. http.route): ');
91-
description = await question('Enter a description: ');
92-
type = await question('Enter the type (string/boolean/integer/double/string[]/boolean[]/integer[]/double[]): ');
93-
piiKey = await question('Does this attribute contain PII? (true/maybe/false): ');
94-
isInOtel = await question('Is this attribute in OpenTelemetry? (true/false): ');
95-
example = await question('Enter an example value (optional): ');
96-
alias = await question('Enter attributes that alias to this attribute (comma-separated, optional): ');
97-
sdks = await question('Enter SDKs that use this attribute (comma-separated, optional): ');
81+
key = await askForAttributeName();
82+
description = await askForAttributeDescription();
83+
type = await askForAttributeType();
84+
piiKey = await askForAttributePii();
85+
isInOtel = String(await askForAttributeIsInOtel());
86+
example = await askForAttributeExample();
87+
alias = await askForAttributeAlias();
88+
sdks = await askForAttributeSdks();
9889
} else {
9990
key = values.key;
10091
description = values.description;
@@ -163,26 +154,140 @@ const createAttribute = async () => {
163154
}
164155

165156
fs.writeFileSync(filePath, `${JSON.stringify(attribute, null, 2)}\n`);
166-
console.log(`Successfully created attribute file at: ${filePath}`);
157+
log.success(`Successfully created attribute file at: ${filePath}`);
167158

168159
// Ask if user wants to generate docs
169-
const generateDocs = await question('\nWould you like to generate documentation? (y/n): ');
170-
if (generateDocs.toLowerCase() === 'y') {
171-
console.log('Generating documentation...');
160+
const generateDocs = await askForGenerateDocs();
161+
if (generateDocs) {
162+
log.info('Generating documentation...');
172163
try {
173164
execSync('yarn run generate', { stdio: 'inherit' });
174-
console.log('Documentation generated successfully!');
165+
log.success('Documentation generated successfully!');
175166
} catch (error) {
176-
console.error('Error generating documentation:', error);
167+
log.error(`Error generating documentation: ${error}`);
177168
process.exit(1);
178169
}
179170
}
180171
} catch (error) {
181-
console.error('Error creating attribute:', error);
172+
log.error(`Error creating attribute: ${error}`);
182173
process.exit(1);
183174
} finally {
184-
rl.close();
175+
outro('Attribute creation done!');
185176
}
186177
};
187178

188179
createAttribute();
180+
181+
async function askForAttributeName() {
182+
return abortIfCancelled(
183+
text({
184+
message: 'Enter the attribute key',
185+
placeholder: 'http.route',
186+
validate: (value) => {
187+
if (!value) {
188+
return 'Attribute key is required';
189+
}
190+
return undefined;
191+
},
192+
}),
193+
);
194+
}
195+
196+
async function askForAttributeDescription() {
197+
return abortIfCancelled(
198+
text({
199+
message: 'Enter the attribute description',
200+
placeholder: 'The route pattern of the request',
201+
validate: (value) => {
202+
if (!value) {
203+
return 'Attribute description is required';
204+
}
205+
return undefined;
206+
},
207+
}),
208+
);
209+
}
210+
211+
async function askForAttributeType() {
212+
return abortIfCancelled(
213+
select({
214+
message: 'Enter the type',
215+
options: [
216+
{ value: 'string', label: 'String' },
217+
{ value: 'integer', label: 'Integer' },
218+
{ value: 'boolean', label: 'Boolean' },
219+
{ value: 'double', label: 'Double' },
220+
{ value: 'string[]', label: 'String Array' },
221+
{ value: 'integer[]', label: 'Integer Array' },
222+
{ value: 'boolean[]', label: 'Boolean Array' },
223+
{ value: 'double[]', label: 'Double Array' },
224+
],
225+
}),
226+
);
227+
}
228+
229+
async function askForAttributePii() {
230+
return abortIfCancelled(
231+
select({
232+
message: 'Does the attribute contain PII?',
233+
options: [
234+
{ value: 'true', label: 'Yes' },
235+
{ value: 'false', label: 'No' },
236+
{ value: 'maybe', label: 'Maybe' },
237+
],
238+
}),
239+
);
240+
}
241+
242+
async function askForAttributeIsInOtel() {
243+
return abortIfCancelled(
244+
confirm({
245+
message: 'Is the attribute in OpenTelemetry?',
246+
initialValue: true,
247+
}),
248+
);
249+
}
250+
251+
async function askForAttributeExample() {
252+
return abortIfCancelled(
253+
text({
254+
message: 'Enter an example value (optional)',
255+
placeholder: 'GET /users/:id',
256+
}),
257+
);
258+
}
259+
260+
async function askForAttributeAlias() {
261+
return abortIfCancelled(
262+
text({
263+
message: 'Enter attributes that alias to this attribute (comma-separated, optional)',
264+
placeholder: 'url.route,http.routename',
265+
}),
266+
);
267+
}
268+
269+
async function askForAttributeSdks() {
270+
return abortIfCancelled(
271+
text({
272+
message: 'Enter SDKs that use this attribute (comma-separated, optional)',
273+
placeholder: 'javascript-browser,javascript-node',
274+
}),
275+
);
276+
}
277+
278+
async function askForGenerateDocs() {
279+
return abortIfCancelled(
280+
confirm({
281+
message: 'Would you like to generate documentation?',
282+
initialValue: true,
283+
}),
284+
);
285+
}
286+
287+
async function abortIfCancelled<T>(input: T | Promise<T>): Promise<Exclude<T, symbol>> {
288+
if (isCancel(await input)) {
289+
process.exit(0);
290+
} else {
291+
return input as Exclude<T, symbol>;
292+
}
293+
}

yarn.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@
7070
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz#4c7afa90e3970213599b4095e62f87e5972b2340"
7171
integrity sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==
7272

73+
74+
version "0.5.0"
75+
resolved "https://registry.yarnpkg.com/@clack/core/-/core-0.5.0.tgz#970df024a927d6af90111667a0384e233b5ffa1a"
76+
integrity sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==
77+
dependencies:
78+
picocolors "^1.0.0"
79+
sisteransi "^1.0.5"
80+
81+
"@clack/prompts@^0.11.0":
82+
version "0.11.0"
83+
resolved "https://registry.yarnpkg.com/@clack/prompts/-/prompts-0.11.0.tgz#5c0218f2b46626a50d72d8a485681eb8d94bd2a7"
84+
integrity sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==
85+
dependencies:
86+
"@clack/core" "0.5.0"
87+
picocolors "^1.0.0"
88+
sisteransi "^1.0.5"
89+
7390
"@esbuild/[email protected]":
7491
version "0.25.2"
7592
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz#b87036f644f572efb2b3c75746c97d1d2d87ace8"
@@ -1511,6 +1528,11 @@ siginfo@^2.0.0:
15111528
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
15121529
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
15131530

1531+
sisteransi@^1.0.5:
1532+
version "1.0.5"
1533+
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
1534+
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
1535+
15141536
source-map-js@^1.0.1, source-map-js@^1.2.1:
15151537
version "1.2.1"
15161538
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"

0 commit comments

Comments
 (0)