workos 0.15.2 → 0.16.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/README.md +37 -11
- package/dist/bin.js +1439 -1257
- package/dist/bin.js.map +1 -1
- package/dist/cli.config.d.ts +1 -0
- package/dist/cli.config.js +1 -0
- package/dist/cli.config.js.map +1 -1
- package/dist/commands/api/index.js +7 -2
- package/dist/commands/api/index.js.map +1 -1
- package/dist/commands/api/interactive.js +9 -3
- package/dist/commands/api/interactive.js.map +1 -1
- package/dist/commands/debug.d.ts +2 -1
- package/dist/commands/debug.js +43 -3
- package/dist/commands/debug.js.map +1 -1
- package/dist/commands/dev.js +5 -4
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.js +13 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/emulate.js +2 -2
- package/dist/commands/emulate.js.map +1 -1
- package/dist/commands/env.js +5 -4
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/install-skill.js +4 -3
- package/dist/commands/install-skill.js.map +1 -1
- package/dist/commands/install.js +2 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/migrations.d.ts +1 -1
- package/dist/commands/migrations.js +4 -1
- package/dist/commands/migrations.js.map +1 -1
- package/dist/commands/telemetry.d.ts +3 -0
- package/dist/commands/telemetry.js +88 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/uninstall-skill.js +3 -2
- package/dist/commands/uninstall-skill.js.map +1 -1
- package/dist/commands/vault-run.d.ts +13 -0
- package/dist/commands/vault-run.js +194 -0
- package/dist/commands/vault-run.js.map +1 -0
- package/dist/commands/vault.d.ts +3 -2
- package/dist/commands/vault.js +41 -8
- package/dist/commands/vault.js.map +1 -1
- package/dist/lib/api-error-handler.d.ts +15 -3
- package/dist/lib/api-error-handler.js +52 -34
- package/dist/lib/api-error-handler.js.map +1 -1
- package/dist/lib/command-aliases.d.ts +8 -0
- package/dist/lib/command-aliases.js +12 -0
- package/dist/lib/command-aliases.js.map +1 -0
- package/dist/lib/constants.d.ts +0 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/device-id.d.ts +25 -0
- package/dist/lib/device-id.js +102 -0
- package/dist/lib/device-id.js.map +1 -0
- package/dist/lib/preferences.d.ts +101 -0
- package/dist/lib/preferences.js +198 -0
- package/dist/lib/preferences.js.map +1 -0
- package/dist/lib/run-with-core.js +15 -15
- package/dist/lib/run-with-core.js.map +1 -1
- package/dist/lib/settings.d.ts +6 -0
- package/dist/lib/settings.js +7 -0
- package/dist/lib/settings.js.map +1 -1
- package/dist/lib/telemetry-notice.d.ts +25 -0
- package/dist/lib/telemetry-notice.js +56 -0
- package/dist/lib/telemetry-notice.js.map +1 -0
- package/dist/test/force-insecure-storage.d.ts +1 -0
- package/dist/test/force-insecure-storage.js +9 -0
- package/dist/test/force-insecure-storage.js.map +1 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.js +38 -0
- package/dist/test/setup.js.map +1 -0
- package/dist/utils/analytics.d.ts +41 -0
- package/dist/utils/analytics.js +199 -12
- package/dist/utils/analytics.js.map +1 -1
- package/dist/utils/box.d.ts +29 -1
- package/dist/utils/box.js +92 -4
- package/dist/utils/box.js.map +1 -1
- package/dist/utils/cli-exit.d.ts +15 -0
- package/dist/utils/cli-exit.js +11 -0
- package/dist/utils/cli-exit.js.map +1 -0
- package/dist/utils/cli-symbols.d.ts +1 -1
- package/dist/utils/command-telemetry.d.ts +17 -0
- package/dist/utils/command-telemetry.js +67 -0
- package/dist/utils/command-telemetry.js.map +1 -0
- package/dist/utils/crash-reporter.d.ts +13 -0
- package/dist/utils/crash-reporter.js +91 -0
- package/dist/utils/crash-reporter.js.map +1 -0
- package/dist/utils/debug.d.ts +1 -0
- package/dist/utils/debug.js +4 -1
- package/dist/utils/debug.js.map +1 -1
- package/dist/utils/exit-codes.d.ts +5 -0
- package/dist/utils/exit-codes.js +30 -1
- package/dist/utils/exit-codes.js.map +1 -1
- package/dist/utils/help-json.d.ts +6 -0
- package/dist/utils/help-json.js +87 -10
- package/dist/utils/help-json.js.map +1 -1
- package/dist/utils/output.d.ts +7 -2
- package/dist/utils/output.js +9 -2
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/telemetry-client.d.ts +30 -2
- package/dist/utils/telemetry-client.js +122 -12
- package/dist/utils/telemetry-client.js.map +1 -1
- package/dist/utils/telemetry-store-forward.d.ts +11 -0
- package/dist/utils/telemetry-store-forward.js +94 -0
- package/dist/utils/telemetry-store-forward.js.map +1 -0
- package/dist/utils/telemetry-types.d.ts +58 -9
- package/dist/utils/telemetry-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,57 +1,75 @@
|
|
|
1
1
|
import { WorkOSApiError } from './workos-api.js';
|
|
2
2
|
import { exitWithError } from '../utils/output.js';
|
|
3
|
-
|
|
4
|
-
* Duck-type check for @workos-inc/node SDK exceptions.
|
|
5
|
-
*
|
|
6
|
-
* The SDK throws typed errors (UnauthorizedException, NotFoundException, etc.)
|
|
7
|
-
* that implement the RequestException interface: { status, message, requestID }.
|
|
8
|
-
* We duck-type rather than instanceof to avoid coupling to the SDK's class hierarchy.
|
|
9
|
-
*/
|
|
10
|
-
function isSdkException(error) {
|
|
3
|
+
export function isSdkException(error) {
|
|
11
4
|
if (!(error instanceof Error))
|
|
12
5
|
return false;
|
|
13
6
|
const e = error;
|
|
14
7
|
return typeof e.status === 'number' && typeof e.requestID === 'string';
|
|
15
8
|
}
|
|
9
|
+
function normalizeApiError(error) {
|
|
10
|
+
if (error instanceof WorkOSApiError) {
|
|
11
|
+
return {
|
|
12
|
+
status: error.statusCode,
|
|
13
|
+
code: error.code,
|
|
14
|
+
errors: error.errors,
|
|
15
|
+
message: error.message,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (isSdkException(error)) {
|
|
19
|
+
return {
|
|
20
|
+
status: error.status,
|
|
21
|
+
code: error.code,
|
|
22
|
+
errors: error.errors,
|
|
23
|
+
message: error.message,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function getApiErrorMessage(error, label) {
|
|
29
|
+
if (error.status === 401)
|
|
30
|
+
return 'Invalid API key. Check your environment configuration.';
|
|
31
|
+
if (error.status === 404)
|
|
32
|
+
return `${label} not found.`;
|
|
33
|
+
if (error.status === 422 && error.errors?.length)
|
|
34
|
+
return error.errors.map((e) => e.message).join(', ');
|
|
35
|
+
return error.message;
|
|
36
|
+
}
|
|
16
37
|
/**
|
|
17
38
|
* Create a resource-specific API error handler.
|
|
18
|
-
* Handles
|
|
19
|
-
*
|
|
39
|
+
* Handles raw fetch errors (WorkOSApiError), SDK exceptions, and the SDK's
|
|
40
|
+
* "errors is not iterable" TypeError from malformed 422 responses.
|
|
41
|
+
*
|
|
42
|
+
* `context` optionally names the specific resource instance (e.g. a vault
|
|
43
|
+
* object name) so 404 messages can be more specific.
|
|
20
44
|
*/
|
|
21
45
|
export function createApiErrorHandler(resourceName) {
|
|
22
|
-
return (error) => {
|
|
23
|
-
|
|
24
|
-
if (error instanceof WorkOSApiError) {
|
|
46
|
+
return (error, context) => {
|
|
47
|
+
if (error instanceof TypeError && error.message.includes('errors is not iterable')) {
|
|
25
48
|
exitWithError({
|
|
26
|
-
code:
|
|
27
|
-
message:
|
|
28
|
-
|
|
29
|
-
: error.statusCode === 404
|
|
30
|
-
? `${resourceName} not found.`
|
|
31
|
-
: error.statusCode === 422 && error.errors?.length
|
|
32
|
-
? error.errors.map((e) => e.message).join(', ')
|
|
33
|
-
: error.message,
|
|
34
|
-
details: error.errors,
|
|
49
|
+
code: 'unprocessable_entity',
|
|
50
|
+
message: `${resourceName} API rejected the request. Check that all required fields are provided.`,
|
|
51
|
+
apiContext: { resource: resourceName },
|
|
35
52
|
});
|
|
36
53
|
}
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
const label = context ? `${resourceName} '${context}'` : resourceName;
|
|
55
|
+
const apiError = normalizeApiError(error);
|
|
56
|
+
if (apiError) {
|
|
57
|
+
const code = apiError.code ?? `http_${apiError.status}`;
|
|
39
58
|
exitWithError({
|
|
40
|
-
code
|
|
41
|
-
message:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
details: error.errors,
|
|
59
|
+
code,
|
|
60
|
+
message: getApiErrorMessage(apiError, label),
|
|
61
|
+
details: apiError.errors,
|
|
62
|
+
apiContext: {
|
|
63
|
+
status: apiError.status,
|
|
64
|
+
code,
|
|
65
|
+
resource: resourceName,
|
|
66
|
+
},
|
|
49
67
|
});
|
|
50
68
|
}
|
|
51
|
-
// 3. Fallback
|
|
52
69
|
exitWithError({
|
|
53
70
|
code: 'unknown_error',
|
|
54
71
|
message: error instanceof Error ? error.message : 'Unknown error',
|
|
72
|
+
apiContext: { resource: resourceName },
|
|
55
73
|
});
|
|
56
74
|
};
|
|
57
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-error-handler.js","sourceRoot":"","sources":["../../src/lib/api-error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD
|
|
1
|
+
{"version":3,"file":"api-error-handler.js","sourceRoot":"","sources":["../../src/lib/api-error-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { WorkOSApiError } from './workos-api.js';\nimport { exitWithError } from '../utils/output.js';\n\nexport function isSdkException(\n error: unknown,\n): error is { status: number; message: string; requestID: string; code?: string; errors?: Array<{ message: string }> } {\n if (!(error instanceof Error)) return false;\n const e = error as Error & { status?: unknown; requestID?: unknown };\n return typeof e.status === 'number' && typeof e.requestID === 'string';\n}\n\ninterface NormalizedApiError {\n status: number;\n code?: string;\n errors?: Array<{ message: string }>;\n message: string;\n}\n\nfunction normalizeApiError(error: unknown): NormalizedApiError | null {\n if (error instanceof WorkOSApiError) {\n return {\n status: error.statusCode,\n code: error.code,\n errors: error.errors,\n message: error.message,\n };\n }\n\n if (isSdkException(error)) {\n return {\n status: error.status,\n code: error.code,\n errors: error.errors,\n message: error.message,\n };\n }\n\n return null;\n}\n\nfunction getApiErrorMessage(error: NormalizedApiError, label: string): string {\n if (error.status === 401) return 'Invalid API key. Check your environment configuration.';\n if (error.status === 404) return `${label} not found.`;\n if (error.status === 422 && error.errors?.length) return error.errors.map((e) => e.message).join(', ');\n return error.message;\n}\n\n/**\n * Create a resource-specific API error handler.\n * Handles raw fetch errors (WorkOSApiError), SDK exceptions, and the SDK's\n * \"errors is not iterable\" TypeError from malformed 422 responses.\n *\n * `context` optionally names the specific resource instance (e.g. a vault\n * object name) so 404 messages can be more specific.\n */\nexport function createApiErrorHandler(resourceName: string) {\n return (error: unknown, context?: string): never => {\n if (error instanceof TypeError && error.message.includes('errors is not iterable')) {\n exitWithError({\n code: 'unprocessable_entity',\n message: `${resourceName} API rejected the request. Check that all required fields are provided.`,\n apiContext: { resource: resourceName },\n });\n }\n\n const label = context ? `${resourceName} '${context}'` : resourceName;\n const apiError = normalizeApiError(error);\n if (apiError) {\n const code = apiError.code ?? `http_${apiError.status}`;\n exitWithError({\n code,\n message: getApiErrorMessage(apiError, label),\n details: apiError.errors,\n apiContext: {\n status: apiError.status,\n code,\n resource: resourceName,\n },\n });\n }\n\n exitWithError({\n code: 'unknown_error',\n message: error instanceof Error ? error.message : 'Unknown error',\n apiContext: { resource: resourceName },\n });\n };\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared canonical command alias map.
|
|
3
|
+
* Single source of truth for both telemetry and help-json.
|
|
4
|
+
*
|
|
5
|
+
* Keys are user-facing aliases, values are canonical command names.
|
|
6
|
+
* Adding an alias here updates both metrics aggregation and --help --json output.
|
|
7
|
+
*/
|
|
8
|
+
export declare const COMMAND_ALIASES: Record<string, string>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared canonical command alias map.
|
|
3
|
+
* Single source of truth for both telemetry and help-json.
|
|
4
|
+
*
|
|
5
|
+
* Keys are user-facing aliases, values are canonical command names.
|
|
6
|
+
* Adding an alias here updates both metrics aggregation and --help --json output.
|
|
7
|
+
*/
|
|
8
|
+
export const COMMAND_ALIASES = {
|
|
9
|
+
org: 'organization',
|
|
10
|
+
claim: 'env.claim',
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=command-aliases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-aliases.js","sourceRoot":"","sources":["../../src/lib/command-aliases.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACrD,GAAG,EAAE,cAAc;IACnB,KAAK,EAAE,WAAW;CACnB,CAAC","sourcesContent":["/**\n * Shared canonical command alias map.\n * Single source of truth for both telemetry and help-json.\n *\n * Keys are user-facing aliases, values are canonical command names.\n * Adding an alias here updates both metrics aggregation and --help --json output.\n */\nexport const COMMAND_ALIASES: Record<string, string> = {\n org: 'organization',\n claim: 'env.claim',\n};\n"]}
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ export declare const WORKOS_DASHBOARD_URL: string;
|
|
|
26
26
|
export declare const ISSUES_URL: string;
|
|
27
27
|
export declare const ANALYTICS_ENABLED: boolean;
|
|
28
28
|
export declare const INSTALLER_INTERACTION_EVENT_NAME: string;
|
|
29
|
-
export declare const WORKOS_TELEMETRY_ENABLED: boolean;
|
|
30
29
|
export declare const OAUTH_PORT: number;
|
|
31
30
|
/**
|
|
32
31
|
* Common glob patterns to ignore when searching for files.
|
package/dist/lib/constants.js
CHANGED
|
@@ -18,7 +18,6 @@ export const WORKOS_DASHBOARD_URL = settings.documentation.dashboardUrl;
|
|
|
18
18
|
export const ISSUES_URL = settings.documentation.issuesUrl;
|
|
19
19
|
export const ANALYTICS_ENABLED = settings.telemetry.enabled;
|
|
20
20
|
export const INSTALLER_INTERACTION_EVENT_NAME = settings.telemetry.eventName;
|
|
21
|
-
export const WORKOS_TELEMETRY_ENABLED = process.env.WORKOS_TELEMETRY !== 'false';
|
|
22
21
|
export const OAUTH_PORT = settings.legacy.oauthPort;
|
|
23
22
|
/**
|
|
24
23
|
* Common glob patterns to ignore when searching for files.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS1C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,aAAa,EAAE,gBAAgB;IAC/B,WAAW,EAAE,cAAc;IAC3B,SAAS,EAAE,YAAY;CACf,CAAC;AAOX,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AAEnF,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;AAE7B,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;AAChD,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC;AACxE,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC;AAC5D,MAAM,CAAC,MAAM,gCAAgC,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;AAC7E,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS1C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,aAAa,EAAE,gBAAgB;IAC/B,WAAW,EAAE,cAAc;IAC3B,SAAS,EAAE,YAAY;CACf,CAAC;AAOX,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AAEnF,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;AAE7B,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;AAChD,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC;AACxE,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC;AAC5D,MAAM,CAAC,MAAM,gCAAgC,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;AAC7E,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;AAEpD;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAa;IACvC,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,cAAc;IACd,aAAa;CACd,CAAC","sourcesContent":["import { getConfig } from './settings.js';\n\n/**\n * Integration identifier type.\n * No longer an enum — each integration self-registers via the auto-discovery registry.\n * The string value matches the integration directory name (e.g., 'nextjs', 'react-router').\n */\nexport type Integration = string;\n\n/**\n * Well-known integration names for backwards compatibility.\n * New integrations do NOT need to be added here — they're auto-discovered.\n */\nexport const KNOWN_INTEGRATIONS = {\n nextjs: 'nextjs',\n react: 'react',\n tanstackStart: 'tanstack-start',\n reactRouter: 'react-router',\n vanillaJs: 'vanilla-js',\n} as const;\n\nexport interface Args {\n debug: boolean;\n integration: Integration;\n}\n\nexport const IS_DEV = ['test', 'development'].includes(process.env.NODE_ENV ?? '');\n\nconst settings = getConfig();\n\nexport const DEBUG = settings.logging.debugMode;\nexport const WORKOS_DOCS_URL = settings.documentation.workosDocsUrl;\nexport const WORKOS_DASHBOARD_URL = settings.documentation.dashboardUrl;\nexport const ISSUES_URL = settings.documentation.issuesUrl;\nexport const ANALYTICS_ENABLED = settings.telemetry.enabled;\nexport const INSTALLER_INTERACTION_EVENT_NAME = settings.telemetry.eventName;\nexport const OAUTH_PORT = settings.legacy.oauthPort;\n\n/**\n * Common glob patterns to ignore when searching for files.\n * Used by multiple integrations.\n */\nexport const IGNORE_PATTERNS: string[] = [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/public/**',\n '**/.next/**',\n];\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent device identifier for telemetry correlation.
|
|
3
|
+
*
|
|
4
|
+
* Stored at ~/.workos/device-id as a plain UTF-8 UUID string. Not a secret
|
|
5
|
+
* — this is a convenience identifier that survives keyring unavailability.
|
|
6
|
+
* Any IO failure falls through to a one-shot UUID for the current session.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Asynchronously resolve (and lazily create) the device id without blocking
|
|
10
|
+
* the event loop. Memoized: the first call performs the IO, concurrent and
|
|
11
|
+
* later callers await the same promise. Populates the shared cache that the
|
|
12
|
+
* synchronous getDeviceId() reads, so prewarming this at startup keeps the
|
|
13
|
+
* synchronous telemetry path off blocking fs IO. Never rejects.
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadDeviceId(): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Synchronous accessor for the telemetry event path. Returns the prewarmed
|
|
18
|
+
* value when loadDeviceId() has run; otherwise falls back to a one-time
|
|
19
|
+
* synchronous read of the same file (returning the persisted id, so the value
|
|
20
|
+
* never diverges from the async path). On any IO failure, returns a one-shot
|
|
21
|
+
* UUID scoped to the current process — never throws.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getDeviceId(): string;
|
|
24
|
+
/** Test seam — resets the in-memory cache between test cases. */
|
|
25
|
+
export declare function __resetDeviceIdCache(): void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent device identifier for telemetry correlation.
|
|
3
|
+
*
|
|
4
|
+
* Stored at ~/.workos/device-id as a plain UTF-8 UUID string. Not a secret
|
|
5
|
+
* — this is a convenience identifier that survives keyring unavailability.
|
|
6
|
+
* Any IO failure falls through to a one-shot UUID for the current session.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import crypto from 'node:crypto';
|
|
13
|
+
// RFC 4122 v4 format — matches what `crypto.randomUUID()` produces.
|
|
14
|
+
// Rejects non-UUID strings like "------------------------------------".
|
|
15
|
+
const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
16
|
+
let cached;
|
|
17
|
+
let pending;
|
|
18
|
+
function getDeviceIdPath() {
|
|
19
|
+
return path.join(os.homedir(), '.workos', 'device-id');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Asynchronously resolve (and lazily create) the device id without blocking
|
|
23
|
+
* the event loop. Memoized: the first call performs the IO, concurrent and
|
|
24
|
+
* later callers await the same promise. Populates the shared cache that the
|
|
25
|
+
* synchronous getDeviceId() reads, so prewarming this at startup keeps the
|
|
26
|
+
* synchronous telemetry path off blocking fs IO. Never rejects.
|
|
27
|
+
*/
|
|
28
|
+
export function loadDeviceId() {
|
|
29
|
+
if (cached)
|
|
30
|
+
return Promise.resolve(cached);
|
|
31
|
+
if (pending)
|
|
32
|
+
return pending;
|
|
33
|
+
pending = (async () => {
|
|
34
|
+
const filePath = getDeviceIdPath();
|
|
35
|
+
try {
|
|
36
|
+
try {
|
|
37
|
+
const raw = (await readFile(filePath, 'utf8')).trim();
|
|
38
|
+
if (UUID_V4_REGEX.test(raw)) {
|
|
39
|
+
cached = raw;
|
|
40
|
+
return raw;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Missing/unreadable file — fall through and create it.
|
|
45
|
+
}
|
|
46
|
+
const id = crypto.randomUUID();
|
|
47
|
+
await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
48
|
+
await writeFile(filePath, id, { encoding: 'utf8', mode: 0o600 });
|
|
49
|
+
cached = id;
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// IO failure (readonly FS, permission denied, etc.) — fall through to
|
|
54
|
+
// a session-scoped UUID, cached for the rest of this process.
|
|
55
|
+
cached = crypto.randomUUID();
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
pending = undefined;
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
return pending;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Synchronous accessor for the telemetry event path. Returns the prewarmed
|
|
66
|
+
* value when loadDeviceId() has run; otherwise falls back to a one-time
|
|
67
|
+
* synchronous read of the same file (returning the persisted id, so the value
|
|
68
|
+
* never diverges from the async path). On any IO failure, returns a one-shot
|
|
69
|
+
* UUID scoped to the current process — never throws.
|
|
70
|
+
*/
|
|
71
|
+
export function getDeviceId() {
|
|
72
|
+
if (cached)
|
|
73
|
+
return cached;
|
|
74
|
+
const filePath = getDeviceIdPath();
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(filePath)) {
|
|
77
|
+
const raw = fs.readFileSync(filePath, 'utf8').trim();
|
|
78
|
+
if (UUID_V4_REGEX.test(raw)) {
|
|
79
|
+
cached = raw;
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const id = crypto.randomUUID();
|
|
84
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
85
|
+
fs.writeFileSync(filePath, id, { encoding: 'utf8', mode: 0o600 });
|
|
86
|
+
cached = id;
|
|
87
|
+
return id;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// IO failure (readonly FS, permission denied, etc.) — fall through to
|
|
91
|
+
// a session-scoped UUID. Cache it so subsequent calls in this process
|
|
92
|
+
// return the same value; the next process run will retry IO.
|
|
93
|
+
cached = crypto.randomUUID();
|
|
94
|
+
return cached;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Test seam — resets the in-memory cache between test cases. */
|
|
98
|
+
export function __resetDeviceIdCache() {
|
|
99
|
+
cached = undefined;
|
|
100
|
+
pending = undefined;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=device-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-id.js","sourceRoot":"","sources":["../../src/lib/device-id.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistent device identifier for telemetry correlation.\n *\n * Stored at ~/.workos/device-id as a plain UTF-8 UUID string. Not a secret\n * — this is a convenience identifier that survives keyring unavailability.\n * Any IO failure falls through to a one-shot UUID for the current session.\n */\n\nimport fs from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\nimport crypto from 'node:crypto';\n\n// RFC 4122 v4 format — matches what `crypto.randomUUID()` produces.\n// Rejects non-UUID strings like \"------------------------------------\".\nconst UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nlet cached: string | undefined;\nlet pending: Promise<string> | undefined;\n\nfunction getDeviceIdPath(): string {\n return path.join(os.homedir(), '.workos', 'device-id');\n}\n\n/**\n * Asynchronously resolve (and lazily create) the device id without blocking\n * the event loop. Memoized: the first call performs the IO, concurrent and\n * later callers await the same promise. Populates the shared cache that the\n * synchronous getDeviceId() reads, so prewarming this at startup keeps the\n * synchronous telemetry path off blocking fs IO. Never rejects.\n */\nexport function loadDeviceId(): Promise<string> {\n if (cached) return Promise.resolve(cached);\n if (pending) return pending;\n\n pending = (async () => {\n const filePath = getDeviceIdPath();\n try {\n try {\n const raw = (await readFile(filePath, 'utf8')).trim();\n if (UUID_V4_REGEX.test(raw)) {\n cached = raw;\n return raw;\n }\n } catch {\n // Missing/unreadable file — fall through and create it.\n }\n\n const id = crypto.randomUUID();\n await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });\n await writeFile(filePath, id, { encoding: 'utf8', mode: 0o600 });\n cached = id;\n return id;\n } catch {\n // IO failure (readonly FS, permission denied, etc.) — fall through to\n // a session-scoped UUID, cached for the rest of this process.\n cached = crypto.randomUUID();\n return cached;\n } finally {\n pending = undefined;\n }\n })();\n\n return pending;\n}\n\n/**\n * Synchronous accessor for the telemetry event path. Returns the prewarmed\n * value when loadDeviceId() has run; otherwise falls back to a one-time\n * synchronous read of the same file (returning the persisted id, so the value\n * never diverges from the async path). On any IO failure, returns a one-shot\n * UUID scoped to the current process — never throws.\n */\nexport function getDeviceId(): string {\n if (cached) return cached;\n\n const filePath = getDeviceIdPath();\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, 'utf8').trim();\n if (UUID_V4_REGEX.test(raw)) {\n cached = raw;\n return raw;\n }\n }\n\n const id = crypto.randomUUID();\n fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });\n fs.writeFileSync(filePath, id, { encoding: 'utf8', mode: 0o600 });\n cached = id;\n return id;\n } catch {\n // IO failure (readonly FS, permission denied, etc.) — fall through to\n // a session-scoped UUID. Cache it so subsequent calls in this process\n // return the same value; the next process run will retry IO.\n cached = crypto.randomUUID();\n return cached;\n }\n}\n\n/** Test seam — resets the in-memory cache between test cases. */\nexport function __resetDeviceIdCache(): void {\n cached = undefined;\n pending = undefined;\n}\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain CLI preferences store.
|
|
3
|
+
*
|
|
4
|
+
* Stored at ~/.workos/preferences.json as plain JSON. These are NOT secrets —
|
|
5
|
+
* knowing that someone opted out of telemetry leaks nothing — so this
|
|
6
|
+
* deliberately avoids the keyring abstraction (config-store.ts) to prevent a
|
|
7
|
+
* non-secret write from ever triggering the insecure-fallback security warning.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the structural pattern of device-id.ts: a synchronous accessor backed
|
|
10
|
+
* by a cache, an async prewarm that populates that cache off the blocking-fs
|
|
11
|
+
* path, and a never-throws contract on every read/parse so a corrupt file can
|
|
12
|
+
* never break a command.
|
|
13
|
+
*/
|
|
14
|
+
export interface CliPreferences {
|
|
15
|
+
telemetry?: {
|
|
16
|
+
/** true => the user explicitly opted out of telemetry. */
|
|
17
|
+
optedOut?: boolean;
|
|
18
|
+
/** ISO timestamp the first-run notice was shown — written in Phase 2 only. */
|
|
19
|
+
noticeShownAt?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Effective source of the resolved telemetry-enabled decision. */
|
|
23
|
+
export type TelemetrySource = 'env' | 'preference' | 'default';
|
|
24
|
+
export declare function getPreferencesPath(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Asynchronously load preferences and warm the cache the synchronous
|
|
27
|
+
* getPreferences() reads. Memoized: the first call performs the IO, concurrent
|
|
28
|
+
* and later callers await the same promise. Prewarming this at startup keeps
|
|
29
|
+
* the synchronous telemetry path off blocking fs IO. Never rejects.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadPreferences(): Promise<CliPreferences>;
|
|
32
|
+
/**
|
|
33
|
+
* Synchronous accessor. Returns the prewarmed value when loadPreferences() has
|
|
34
|
+
* run; otherwise performs a one-time synchronous read of the same file. On any
|
|
35
|
+
* IO/parse failure returns {} (telemetry stays at its default-on state). Never
|
|
36
|
+
* throws.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getPreferences(): CliPreferences;
|
|
39
|
+
/**
|
|
40
|
+
* Read-modify-write the on-disk preferences, merging `next` over the current
|
|
41
|
+
* persisted value so future fields are never clobbered, then update the cache.
|
|
42
|
+
* Throws on write failure so callers on the command path (opt-out/opt-in) can
|
|
43
|
+
* surface a clear error — the read path swallows, the write path does not.
|
|
44
|
+
*/
|
|
45
|
+
export declare function savePreferences(next: CliPreferences): void;
|
|
46
|
+
/** Whether the user has explicitly opted out via the saved preference. */
|
|
47
|
+
export declare function isTelemetryOptedOut(): boolean;
|
|
48
|
+
/** Persist the opt-out flag. Throws on write failure (see savePreferences). */
|
|
49
|
+
export declare function setTelemetryOptedOut(value: boolean): void;
|
|
50
|
+
/** Whether the first-run telemetry notice has already been shown (ever). */
|
|
51
|
+
export declare function isNoticeShown(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Persist the first-run notice as shown, stamping the current time. Uses the
|
|
54
|
+
* read-modify-write savePreferences so it never clobbers the optedOut flag.
|
|
55
|
+
* Throws on write failure (see savePreferences) — the caller in
|
|
56
|
+
* telemetry-notice.ts swallows it so a read-only FS never blocks a command.
|
|
57
|
+
*/
|
|
58
|
+
export declare function markNoticeShown(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Tri-state env override for telemetry.
|
|
61
|
+
*
|
|
62
|
+
* Only the explicit strings 'true' / 'false' count as an override. Any other
|
|
63
|
+
* value — including unset or garbage like '1' — returns undefined and falls
|
|
64
|
+
* through to the saved preference.
|
|
65
|
+
*
|
|
66
|
+
* This is a deliberate, documented change from the old `WORKOS_TELEMETRY !==
|
|
67
|
+
* 'false'` behaviour: previously `WORKOS_TELEMETRY=1` forced telemetry on even
|
|
68
|
+
* for opted-out users; now an opt-out is respected unless the env var
|
|
69
|
+
* explicitly says 'true'.
|
|
70
|
+
*/
|
|
71
|
+
export declare function envTelemetryOverride(): boolean | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Effective telemetry-enabled decision.
|
|
74
|
+
*
|
|
75
|
+
* Resolution order:
|
|
76
|
+
* 1. envTelemetryOverride() if defined — env wins in BOTH directions.
|
|
77
|
+
* 2. otherwise !isTelemetryOptedOut() — default-on unless explicitly opted out.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isTelemetryEnabled(): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Which signal produced the effective telemetry decision. Mirrors the
|
|
82
|
+
* precedence in isTelemetryEnabled() so the `telemetry status` command and the
|
|
83
|
+
* resolver can never drift.
|
|
84
|
+
*
|
|
85
|
+
* Note: an explicit opt-in (optedOut === false) reads as 'default', not
|
|
86
|
+
* 'preference' — its outcome is identical to a fresh install, and the resolver
|
|
87
|
+
* only treats optedOut === true as a non-default signal, so reporting
|
|
88
|
+
* 'preference' here would imply a behavioral difference that does not exist.
|
|
89
|
+
*/
|
|
90
|
+
export declare function getTelemetrySource(): TelemetrySource;
|
|
91
|
+
/**
|
|
92
|
+
* Delete the preferences file, returning telemetry to its fresh-install state
|
|
93
|
+
* (opted-in, first-run notice unseen). Used by `debug reset` to wipe stored CLI
|
|
94
|
+
* state alongside credentials and config. No-op if the file does not exist;
|
|
95
|
+
* throws on a real delete failure (e.g. permission denied) so the caller can
|
|
96
|
+
* surface it, mirroring clearConfig/clearCredentials. Resets the in-memory
|
|
97
|
+
* cache so subsequent reads in this process reflect the cleared state.
|
|
98
|
+
*/
|
|
99
|
+
export declare function clearPreferences(): void;
|
|
100
|
+
/** Test seam — resets the in-memory cache between test cases. */
|
|
101
|
+
export declare function __resetPreferencesCache(): void;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain CLI preferences store.
|
|
3
|
+
*
|
|
4
|
+
* Stored at ~/.workos/preferences.json as plain JSON. These are NOT secrets —
|
|
5
|
+
* knowing that someone opted out of telemetry leaks nothing — so this
|
|
6
|
+
* deliberately avoids the keyring abstraction (config-store.ts) to prevent a
|
|
7
|
+
* non-secret write from ever triggering the insecure-fallback security warning.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the structural pattern of device-id.ts: a synchronous accessor backed
|
|
10
|
+
* by a cache, an async prewarm that populates that cache off the blocking-fs
|
|
11
|
+
* path, and a never-throws contract on every read/parse so a corrupt file can
|
|
12
|
+
* never break a command.
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import { readFile } from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
let cached;
|
|
19
|
+
let pending;
|
|
20
|
+
export function getPreferencesPath() {
|
|
21
|
+
return path.join(os.homedir(), '.workos', 'preferences.json');
|
|
22
|
+
}
|
|
23
|
+
function parsePreferences(raw) {
|
|
24
|
+
try {
|
|
25
|
+
const value = JSON.parse(raw);
|
|
26
|
+
// Defend against a file that parses to a non-object (e.g. `"true"`, `42`).
|
|
27
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Corrupt JSON — fall through to empty preferences. A later savePreferences
|
|
33
|
+
// overwrites it cleanly; we never auto-delete.
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Asynchronously load preferences and warm the cache the synchronous
|
|
39
|
+
* getPreferences() reads. Memoized: the first call performs the IO, concurrent
|
|
40
|
+
* and later callers await the same promise. Prewarming this at startup keeps
|
|
41
|
+
* the synchronous telemetry path off blocking fs IO. Never rejects.
|
|
42
|
+
*/
|
|
43
|
+
export function loadPreferences() {
|
|
44
|
+
if (cached)
|
|
45
|
+
return Promise.resolve(cached);
|
|
46
|
+
if (pending)
|
|
47
|
+
return pending;
|
|
48
|
+
pending = (async () => {
|
|
49
|
+
try {
|
|
50
|
+
const raw = await readFile(getPreferencesPath(), 'utf8');
|
|
51
|
+
cached = parsePreferences(raw);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Missing/unreadable file — treat as no preferences set.
|
|
55
|
+
cached = {};
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
pending = undefined;
|
|
59
|
+
}
|
|
60
|
+
return cached;
|
|
61
|
+
})();
|
|
62
|
+
return pending;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Synchronous accessor. Returns the prewarmed value when loadPreferences() has
|
|
66
|
+
* run; otherwise performs a one-time synchronous read of the same file. On any
|
|
67
|
+
* IO/parse failure returns {} (telemetry stays at its default-on state). Never
|
|
68
|
+
* throws.
|
|
69
|
+
*/
|
|
70
|
+
export function getPreferences() {
|
|
71
|
+
if (cached)
|
|
72
|
+
return cached;
|
|
73
|
+
try {
|
|
74
|
+
const raw = fs.readFileSync(getPreferencesPath(), 'utf8');
|
|
75
|
+
cached = parsePreferences(raw);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Missing/unreadable/corrupt — telemetry stays at its default-on state.
|
|
79
|
+
cached = {};
|
|
80
|
+
}
|
|
81
|
+
return cached;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read-modify-write the on-disk preferences, merging `next` over the current
|
|
85
|
+
* persisted value so future fields are never clobbered, then update the cache.
|
|
86
|
+
* Throws on write failure so callers on the command path (opt-out/opt-in) can
|
|
87
|
+
* surface a clear error — the read path swallows, the write path does not.
|
|
88
|
+
*/
|
|
89
|
+
export function savePreferences(next) {
|
|
90
|
+
const filePath = getPreferencesPath();
|
|
91
|
+
// Read-modify-write over the CURRENT on-disk value (not the cache) so a field
|
|
92
|
+
// written by another process / a future phase is preserved.
|
|
93
|
+
let current = {};
|
|
94
|
+
try {
|
|
95
|
+
current = parsePreferences(fs.readFileSync(filePath, 'utf8'));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// No existing file (or unreadable) — start from empty.
|
|
99
|
+
}
|
|
100
|
+
const merged = {
|
|
101
|
+
...current,
|
|
102
|
+
...next,
|
|
103
|
+
...(current.telemetry || next.telemetry ? { telemetry: { ...current.telemetry, ...next.telemetry } } : {}),
|
|
104
|
+
};
|
|
105
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
106
|
+
fs.writeFileSync(filePath, JSON.stringify(merged), { encoding: 'utf8', mode: 0o600 });
|
|
107
|
+
cached = merged;
|
|
108
|
+
}
|
|
109
|
+
/** Whether the user has explicitly opted out via the saved preference. */
|
|
110
|
+
export function isTelemetryOptedOut() {
|
|
111
|
+
return getPreferences().telemetry?.optedOut === true;
|
|
112
|
+
}
|
|
113
|
+
/** Persist the opt-out flag. Throws on write failure (see savePreferences). */
|
|
114
|
+
export function setTelemetryOptedOut(value) {
|
|
115
|
+
savePreferences({ telemetry: { optedOut: value } });
|
|
116
|
+
}
|
|
117
|
+
/** Whether the first-run telemetry notice has already been shown (ever). */
|
|
118
|
+
export function isNoticeShown() {
|
|
119
|
+
return !!getPreferences().telemetry?.noticeShownAt;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Persist the first-run notice as shown, stamping the current time. Uses the
|
|
123
|
+
* read-modify-write savePreferences so it never clobbers the optedOut flag.
|
|
124
|
+
* Throws on write failure (see savePreferences) — the caller in
|
|
125
|
+
* telemetry-notice.ts swallows it so a read-only FS never blocks a command.
|
|
126
|
+
*/
|
|
127
|
+
export function markNoticeShown() {
|
|
128
|
+
savePreferences({ telemetry: { noticeShownAt: new Date().toISOString() } });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Tri-state env override for telemetry.
|
|
132
|
+
*
|
|
133
|
+
* Only the explicit strings 'true' / 'false' count as an override. Any other
|
|
134
|
+
* value — including unset or garbage like '1' — returns undefined and falls
|
|
135
|
+
* through to the saved preference.
|
|
136
|
+
*
|
|
137
|
+
* This is a deliberate, documented change from the old `WORKOS_TELEMETRY !==
|
|
138
|
+
* 'false'` behaviour: previously `WORKOS_TELEMETRY=1` forced telemetry on even
|
|
139
|
+
* for opted-out users; now an opt-out is respected unless the env var
|
|
140
|
+
* explicitly says 'true'.
|
|
141
|
+
*/
|
|
142
|
+
export function envTelemetryOverride() {
|
|
143
|
+
const value = process.env.WORKOS_TELEMETRY;
|
|
144
|
+
if (value === 'true')
|
|
145
|
+
return true;
|
|
146
|
+
if (value === 'false')
|
|
147
|
+
return false;
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Effective telemetry-enabled decision.
|
|
152
|
+
*
|
|
153
|
+
* Resolution order:
|
|
154
|
+
* 1. envTelemetryOverride() if defined — env wins in BOTH directions.
|
|
155
|
+
* 2. otherwise !isTelemetryOptedOut() — default-on unless explicitly opted out.
|
|
156
|
+
*/
|
|
157
|
+
export function isTelemetryEnabled() {
|
|
158
|
+
const override = envTelemetryOverride();
|
|
159
|
+
if (override !== undefined)
|
|
160
|
+
return override;
|
|
161
|
+
return !isTelemetryOptedOut();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Which signal produced the effective telemetry decision. Mirrors the
|
|
165
|
+
* precedence in isTelemetryEnabled() so the `telemetry status` command and the
|
|
166
|
+
* resolver can never drift.
|
|
167
|
+
*
|
|
168
|
+
* Note: an explicit opt-in (optedOut === false) reads as 'default', not
|
|
169
|
+
* 'preference' — its outcome is identical to a fresh install, and the resolver
|
|
170
|
+
* only treats optedOut === true as a non-default signal, so reporting
|
|
171
|
+
* 'preference' here would imply a behavioral difference that does not exist.
|
|
172
|
+
*/
|
|
173
|
+
export function getTelemetrySource() {
|
|
174
|
+
if (envTelemetryOverride() !== undefined)
|
|
175
|
+
return 'env';
|
|
176
|
+
if (isTelemetryOptedOut())
|
|
177
|
+
return 'preference';
|
|
178
|
+
return 'default';
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Delete the preferences file, returning telemetry to its fresh-install state
|
|
182
|
+
* (opted-in, first-run notice unseen). Used by `debug reset` to wipe stored CLI
|
|
183
|
+
* state alongside credentials and config. No-op if the file does not exist;
|
|
184
|
+
* throws on a real delete failure (e.g. permission denied) so the caller can
|
|
185
|
+
* surface it, mirroring clearConfig/clearCredentials. Resets the in-memory
|
|
186
|
+
* cache so subsequent reads in this process reflect the cleared state.
|
|
187
|
+
*/
|
|
188
|
+
export function clearPreferences() {
|
|
189
|
+
fs.rmSync(getPreferencesPath(), { force: true });
|
|
190
|
+
cached = {};
|
|
191
|
+
pending = undefined;
|
|
192
|
+
}
|
|
193
|
+
/** Test seam — resets the in-memory cache between test cases. */
|
|
194
|
+
export function __resetPreferencesCache() {
|
|
195
|
+
cached = undefined;
|
|
196
|
+
pending = undefined;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=preferences.js.map
|