@superblocksteam/shared 0.9607.0 → 0.9607.2
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/dist/types/ai/index.d.ts +4 -0
- package/dist/types/ai/index.d.ts.map +1 -1
- package/dist/types/ai/index.js.map +1 -1
- package/dist/types/application/index.d.ts +34 -0
- package/dist/types/application/index.d.ts.map +1 -1
- package/dist/types/application/index.js +48 -1
- package/dist/types/application/index.js.map +1 -1
- package/dist/types/application/index.test.js +61 -0
- package/dist/types/application/index.test.js.map +1 -1
- package/dist/types/event/index.d.ts +23 -1
- package/dist/types/event/index.d.ts.map +1 -1
- package/dist/types/event/index.js +4 -0
- package/dist/types/event/index.js.map +1 -1
- package/dist/types/organization/agent.d.ts +2 -1
- package/dist/types/organization/agent.d.ts.map +1 -1
- package/dist/types/organization/agent.js.map +1 -1
- package/dist/types/organization/index.d.ts +2 -1
- package/dist/types/organization/index.d.ts.map +1 -1
- package/dist/types/organization/index.js.map +1 -1
- package/dist/utils/index.d.ts +8 -7
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +8 -7
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/maskCredential.d.ts +10 -0
- package/dist/utils/maskCredential.d.ts.map +1 -0
- package/dist/utils/maskCredential.js +17 -0
- package/dist/utils/maskCredential.js.map +1 -0
- package/dist/utils/maskCredential.test.d.ts +2 -0
- package/dist/utils/maskCredential.test.d.ts.map +1 -0
- package/dist/utils/maskCredential.test.js +16 -0
- package/dist/utils/maskCredential.test.js.map +1 -0
- package/dist-esm/types/ai/index.d.ts +4 -0
- package/dist-esm/types/ai/index.d.ts.map +1 -1
- package/dist-esm/types/ai/index.js.map +1 -1
- package/dist-esm/types/application/index.d.ts +34 -0
- package/dist-esm/types/application/index.d.ts.map +1 -1
- package/dist-esm/types/application/index.js +46 -0
- package/dist-esm/types/application/index.js.map +1 -1
- package/dist-esm/types/application/index.test.js +62 -1
- package/dist-esm/types/application/index.test.js.map +1 -1
- package/dist-esm/types/event/index.d.ts +23 -1
- package/dist-esm/types/event/index.d.ts.map +1 -1
- package/dist-esm/types/event/index.js +4 -0
- package/dist-esm/types/event/index.js.map +1 -1
- package/dist-esm/types/organization/agent.d.ts +2 -1
- package/dist-esm/types/organization/agent.d.ts.map +1 -1
- package/dist-esm/types/organization/agent.js.map +1 -1
- package/dist-esm/types/organization/index.d.ts +2 -1
- package/dist-esm/types/organization/index.d.ts.map +1 -1
- package/dist-esm/types/organization/index.js.map +1 -1
- package/dist-esm/utils/index.d.ts +8 -7
- package/dist-esm/utils/index.d.ts.map +1 -1
- package/dist-esm/utils/index.js +8 -7
- package/dist-esm/utils/index.js.map +1 -1
- package/dist-esm/utils/maskCredential.d.ts +10 -0
- package/dist-esm/utils/maskCredential.d.ts.map +1 -0
- package/dist-esm/utils/maskCredential.js +13 -0
- package/dist-esm/utils/maskCredential.js.map +1 -0
- package/dist-esm/utils/maskCredential.test.d.ts +2 -0
- package/dist-esm/utils/maskCredential.test.d.ts.map +1 -0
- package/dist-esm/utils/maskCredential.test.js +14 -0
- package/dist-esm/utils/maskCredential.test.js.map +1 -0
- package/package.json +1 -1
- package/src/types/ai/index.ts +5 -0
- package/src/types/application/index.test.ts +84 -1
- package/src/types/application/index.ts +50 -0
- package/src/types/event/index.ts +23 -1
- package/src/types/organization/agent.ts +2 -1
- package/src/types/organization/index.ts +2 -1
- package/src/utils/index.ts +8 -7
- package/src/utils/maskCredential.test.ts +17 -0
- package/src/utils/maskCredential.ts +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maskCredential.js","sourceRoot":"","sources":["../../src/utils/maskCredential.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C,wFAAwF;AACxF,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAW,EAAU,EAAE;IACpD,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC;IACtD,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,0BAA0B,CAAC,EAAE,CAAC;AAC9D,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maskCredential.test.d.ts","sourceRoot":"","sources":["../../src/utils/maskCredential.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { maskCredential } from './maskCredential.js';
|
|
3
|
+
describe('maskCredential', () => {
|
|
4
|
+
it('shows the first 4 characters followed by asterisks', () => {
|
|
5
|
+
expect(maskCredential('abcdEFGHijklMNOP')).toBe('abcd**************');
|
|
6
|
+
});
|
|
7
|
+
it('masks a short key without exposing more than the prefix length', () => {
|
|
8
|
+
expect(maskCredential('ab')).toBe('ab**************');
|
|
9
|
+
});
|
|
10
|
+
it('returns only asterisks for an empty string', () => {
|
|
11
|
+
expect(maskCredential('')).toBe('**************');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
//# sourceMappingURL=maskCredential.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maskCredential.test.js","sourceRoot":"","sources":["../../src/utils/maskCredential.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
package/src/types/ai/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DeploymentTypeEnum, RegionEnum } from '../common/index.js';
|
|
2
|
+
|
|
1
3
|
export type AiChatMessagePayload = Record<string, unknown>;
|
|
2
4
|
|
|
3
5
|
export type AiChatMessageDto = {
|
|
@@ -92,6 +94,9 @@ export interface AiWorkflowAttributes {
|
|
|
92
94
|
export interface AiWorkflowEvent {
|
|
93
95
|
promptId: string;
|
|
94
96
|
attributes: AiWorkflowAttributes;
|
|
97
|
+
region?: RegionEnum;
|
|
98
|
+
deploymentType?: DeploymentTypeEnum;
|
|
99
|
+
deploymentName?: string;
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
/**
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
1
4
|
import { describe, expect, it } from 'vitest';
|
|
2
5
|
|
|
3
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ApplicationType,
|
|
8
|
+
effectiveCommitTemplateName,
|
|
9
|
+
getApplicationType,
|
|
10
|
+
isSdkApiCommitTemplate,
|
|
11
|
+
isSdkApiTemplate,
|
|
12
|
+
LEGACY_COMMIT_TEMPLATE_NAME
|
|
13
|
+
} from './index';
|
|
4
14
|
|
|
5
15
|
describe('SDK API application templates', () => {
|
|
6
16
|
it('classifies app-fullstack as the 3.0 SDK API template', () => {
|
|
@@ -20,3 +30,76 @@ describe('SDK API application templates', () => {
|
|
|
20
30
|
expect(effectiveCommitTemplateName(null)).toBe(LEGACY_COMMIT_TEMPLATE_NAME);
|
|
21
31
|
});
|
|
22
32
|
});
|
|
33
|
+
|
|
34
|
+
describe('getApplicationType', () => {
|
|
35
|
+
it('classifies an application with devEnvEnabled=false as 1.0', () => {
|
|
36
|
+
expect(getApplicationType({ devEnvEnabled: false, templateName: null })).toBe(ApplicationType.V1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('classifies a legacy 1.0 application as 1.0 even when a template name is present', () => {
|
|
40
|
+
// devEnvEnabled is the 1.0 vs 2.0+ discriminator; template name alone never promotes a 1.0 app.
|
|
41
|
+
expect(getApplicationType({ devEnvEnabled: false, templateName: 'app-fullstack' })).toBe(ApplicationType.V1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it.each([null, 'shadcn-demo-app', 'blank-app'])('classifies a code-mode application on template %s as 2.0', (templateName) => {
|
|
45
|
+
expect(getApplicationType({ devEnvEnabled: true, templateName })).toBe(ApplicationType.V2);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('classifies a code-mode application on an SDK API template as 3.0', () => {
|
|
49
|
+
expect(getApplicationType({ devEnvEnabled: true, templateName: 'app-fullstack' })).toBe(ApplicationType.V3);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it.each([undefined, null])('returns undefined when devEnvEnabled was not projected (%s) by the query', (devEnvEnabled) => {
|
|
53
|
+
// dev_env_enabled is NOT NULL DEFAULT false, so a nullish value means unselected.
|
|
54
|
+
expect(getApplicationType({ devEnvEnabled, templateName: 'app-fullstack' })).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns undefined when templateName was not projected for a code-mode app', () => {
|
|
58
|
+
// Guessing 2.0 here would silently misclassify every 3.0 app behind a partial select.
|
|
59
|
+
expect(getApplicationType({ devEnvEnabled: true })).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not need templateName to classify a legacy app', () => {
|
|
63
|
+
expect(getApplicationType({ devEnvEnabled: false })).toBe(ApplicationType.V1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('treats a genuinely null template as a real value, not an unprojected one', () => {
|
|
67
|
+
expect(getApplicationType({ devEnvEnabled: true, templateName: null })).toBe(ApplicationType.V2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('does not classify from a commit-scoped template resolution', () => {
|
|
71
|
+
// A commit predating a 2.0 -> 3.0 migration resolves to the legacy template.
|
|
72
|
+
// Classifying off that instead of the app row is what would misreport 3.0 as 2.0.
|
|
73
|
+
const migratedAppRow = { devEnvEnabled: true, templateName: 'app-fullstack' };
|
|
74
|
+
const preMigrationCommitTemplate = effectiveCommitTemplateName(null);
|
|
75
|
+
|
|
76
|
+
expect(getApplicationType(migratedAppRow)).toBe(ApplicationType.V3);
|
|
77
|
+
expect(getApplicationType({ devEnvEnabled: true, templateName: preMigrationCommitTemplate })).toBe(ApplicationType.V2);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('exposes the version strings the analytics pipeline filters on', () => {
|
|
81
|
+
expect(ApplicationType.V1).toBe('1.0');
|
|
82
|
+
expect(ApplicationType.V2).toBe('2.0');
|
|
83
|
+
expect(ApplicationType.V3).toBe('3.0');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('SDK_API_TEMPLATES stays in sync with the demo-apps template config', () => {
|
|
88
|
+
// isSdkApiTemplate drives the 3.0-vs-2.0 half of getApplicationType, which is a
|
|
89
|
+
// billing input. A new SDK API template shipped without updating the allowlist
|
|
90
|
+
// would not fail safe -- it would confidently classify those apps as 2.0. The
|
|
91
|
+
// template config is the source of truth, so pin the two together here rather
|
|
92
|
+
// than relying on the "must stay in sync" comment alone.
|
|
93
|
+
// This package compiles to CommonJS, so import.meta is unavailable; vitest runs
|
|
94
|
+
// with the package directory as cwd.
|
|
95
|
+
const configPath = resolve(process.cwd(), '../demo-apps/config.json');
|
|
96
|
+
const templates = (JSON.parse(readFileSync(configPath, 'utf8')) as { templates: Record<string, { sdkApi?: boolean }> }).templates;
|
|
97
|
+
|
|
98
|
+
it('has at least one template to check', () => {
|
|
99
|
+
expect(Object.keys(templates).length).toBeGreaterThan(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it.each(Object.entries(templates))('agrees with config.json on whether %s is an SDK API template', (name, template) => {
|
|
103
|
+
expect(isSdkApiTemplate(name)).toBe(template.sdkApi === true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -659,6 +659,56 @@ export function isSdkApiTemplate(templateName: string | null | undefined): boole
|
|
|
659
659
|
return SDK_API_TEMPLATES.has(templateName);
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
+
/**
|
|
663
|
+
* The product generation an application belongs to, as reported to analytics and
|
|
664
|
+
* billing. The values are the customer-facing version labels so downstream
|
|
665
|
+
* consumers (Snowflake, billing) can filter on them directly.
|
|
666
|
+
*/
|
|
667
|
+
export enum ApplicationType {
|
|
668
|
+
/** Legacy drag-and-drop application (not code mode). */
|
|
669
|
+
V1 = '1.0',
|
|
670
|
+
/** Code-mode application on a non-SDK-API template. */
|
|
671
|
+
V2 = '2.0',
|
|
672
|
+
/** Code-mode application on an SDK API template. */
|
|
673
|
+
V3 = '3.0'
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Classifies an application as 1.0 / 2.0 / 3.0 from the two fields that already
|
|
678
|
+
* live on the application row: `dev_env_enabled` separates legacy (1.0) from
|
|
679
|
+
* code-mode (2.0+), and the SDK API template separates 3.0 from 2.0. A 2.0 to
|
|
680
|
+
* 3.0 migration is a `template_name` flip on the same application row, so this
|
|
681
|
+
* classification follows the app across a migration with no extra lookup.
|
|
682
|
+
*
|
|
683
|
+
* Both inputs must come from the application ROW. In particular `templateName`
|
|
684
|
+
* must not be a commit's effective template (see `effectiveCommitTemplateName`):
|
|
685
|
+
* a commit predating a 2.0 to 3.0 migration resolves to the legacy template and
|
|
686
|
+
* would misreport a 3.0 app as 2.0.
|
|
687
|
+
*
|
|
688
|
+
* Returns `undefined` when the app cannot be classified because a field was not
|
|
689
|
+
* projected by the caller's query. `undefined` is distinguishable from a real
|
|
690
|
+
* `null` template (a legacy template, a valid 2.0 value), so an unselected
|
|
691
|
+
* column yields no answer instead of a confidently wrong one.
|
|
692
|
+
*/
|
|
693
|
+
export function getApplicationType(application: {
|
|
694
|
+
devEnvEnabled?: boolean | null;
|
|
695
|
+
templateName?: string | null;
|
|
696
|
+
}): ApplicationType | undefined {
|
|
697
|
+
// `dev_env_enabled` is NOT NULL DEFAULT false, so a nullish value here means
|
|
698
|
+
// the column was not selected rather than a legitimate absence.
|
|
699
|
+
if (application.devEnvEnabled == null) {
|
|
700
|
+
return undefined;
|
|
701
|
+
}
|
|
702
|
+
if (!application.devEnvEnabled) {
|
|
703
|
+
return ApplicationType.V1;
|
|
704
|
+
}
|
|
705
|
+
// `template_name` IS nullable, so only `undefined` means "not projected".
|
|
706
|
+
if (application.templateName === undefined) {
|
|
707
|
+
return undefined;
|
|
708
|
+
}
|
|
709
|
+
return isSdkApiTemplate(application.templateName) ? ApplicationType.V3 : ApplicationType.V2;
|
|
710
|
+
}
|
|
711
|
+
|
|
662
712
|
/**
|
|
663
713
|
* The legacy template name that commits predating the per-commit `template_name`
|
|
664
714
|
* column are treated as. NULL on `application_commit.template_name` means the
|
package/src/types/event/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ApplicationType } from '../application/index.js';
|
|
2
|
+
import { DeploymentTypeEnum, RegionEnum } from '../common/index.js';
|
|
2
3
|
import { RequestSource } from '../user/index.js';
|
|
3
4
|
|
|
4
5
|
export enum EventEntityType {
|
|
@@ -18,6 +19,7 @@ export enum EventEntityType {
|
|
|
18
19
|
SURVEY = 'survey',
|
|
19
20
|
ACCOUNT = 'account',
|
|
20
21
|
BILLING = 'billing',
|
|
22
|
+
BUILDER_LICENSE = 'builder_license',
|
|
21
23
|
OBSERVABILITY = 'observability',
|
|
22
24
|
GROUP = 'group',
|
|
23
25
|
ORGANIZATION_INVITES = 'organization_invites',
|
|
@@ -71,15 +73,18 @@ export enum EventAction {
|
|
|
71
73
|
DEPLOYED = 'deployed',
|
|
72
74
|
UNDEPLOYED = 'undeployed',
|
|
73
75
|
IMPORTED = 'imported',
|
|
76
|
+
MIGRATED = 'migrated',
|
|
74
77
|
EXECUTED = 'executed',
|
|
75
78
|
EXPORTED = 'exported',
|
|
76
79
|
VIEWED = 'viewed',
|
|
77
80
|
STARTED = 'started',
|
|
78
81
|
FINISHED = 'finished',
|
|
79
82
|
ADDED = 'added',
|
|
83
|
+
ASSIGNED = 'assigned',
|
|
80
84
|
PAUSED = 'paused',
|
|
81
85
|
RESUMED = 'resumed',
|
|
82
86
|
UPDATED = 'updated',
|
|
87
|
+
UNASSIGNED = 'unassigned',
|
|
83
88
|
PATCHED = 'patched',
|
|
84
89
|
METADATA_UPDATED = 'metadata_updated',
|
|
85
90
|
SETTINGS_UPDATED = 'settings_updated',
|
|
@@ -124,12 +129,29 @@ export interface UserEvent {
|
|
|
124
129
|
properties?: Record<string, any>;
|
|
125
130
|
createdAt: Date;
|
|
126
131
|
region?: RegionEnum;
|
|
132
|
+
deploymentType?: DeploymentTypeEnum;
|
|
133
|
+
deploymentName?: string;
|
|
127
134
|
source?: RequestSource;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
export interface EventEntity {
|
|
131
138
|
id: string;
|
|
132
139
|
name: string | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Product generation (1.0 / 2.0 / 3.0), stamped onto `* application` events.
|
|
142
|
+
*
|
|
143
|
+
* Populated only by `applicationEventEntity()`, which classifies from the
|
|
144
|
+
* application row. No `ApplicationV2` or `ApplicationV2Dto` carries a field of
|
|
145
|
+
* this name, so passing one of those directly leaves it unset rather than
|
|
146
|
+
* supplying a value of unknown grain -- but note that is a naming convention,
|
|
147
|
+
* not something the type system enforces: `newEvent` accepts any `EventEntity`,
|
|
148
|
+
* so a future field with this name would be picked up structurally.
|
|
149
|
+
*
|
|
150
|
+
* Left undefined whenever the caller cannot classify the app (only an id in
|
|
151
|
+
* hand, or a query that did not project the needed columns). An absent property
|
|
152
|
+
* beats a guessed one -- a wrong generation is worse for billing than none.
|
|
153
|
+
*/
|
|
154
|
+
applicationType?: ApplicationType;
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
export enum EventSource {
|
|
@@ -44,7 +44,8 @@ export interface Organization {
|
|
|
44
44
|
name: string;
|
|
45
45
|
domains?: Domain[];
|
|
46
46
|
agents?: Agent[];
|
|
47
|
-
|
|
47
|
+
/** Live ORG_USER key; omitted on ordinary reads (SEC-43). Use execution-token endpoints. */
|
|
48
|
+
apiKey?: string;
|
|
48
49
|
agentType: AgentType;
|
|
49
50
|
minExternalAgentVersion: string;
|
|
50
51
|
pluginExecutionVersions?: PluginExecutionVersions;
|
package/src/utils/index.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './attachment-upload.js';
|
|
1
|
+
export * from './apiv2.js';
|
|
3
2
|
export * from './attachment-delivery.js';
|
|
3
|
+
export * from './attachment-upload.js';
|
|
4
|
+
export * from './bytestring.js';
|
|
5
|
+
export * from './configuration.js';
|
|
4
6
|
export * from './env.js';
|
|
5
7
|
export * from './environment.js';
|
|
6
8
|
export * from './git-url.js';
|
|
9
|
+
export * from './maskCredential.js';
|
|
10
|
+
export * from './normalize.js';
|
|
11
|
+
export * from './normalizeRoleName.js';
|
|
12
|
+
export * from './page.js';
|
|
7
13
|
export * from './string.js';
|
|
8
14
|
export * from './time.js';
|
|
9
15
|
export * from './truncate.js';
|
|
10
16
|
export * from './url.js';
|
|
11
|
-
export * from './bytestring.js';
|
|
12
|
-
export * from './apiv2.js';
|
|
13
|
-
export * from './page.js';
|
|
14
|
-
export * from './normalize.js';
|
|
15
|
-
export * from './normalizeRoleName.js';
|
|
16
17
|
|
|
17
18
|
import gridV4 from './pageFixtures/v4/grid.js';
|
|
18
19
|
export { gridV4 };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { maskCredential } from './maskCredential.js';
|
|
4
|
+
|
|
5
|
+
describe('maskCredential', () => {
|
|
6
|
+
it('shows the first 4 characters followed by asterisks', () => {
|
|
7
|
+
expect(maskCredential('abcdEFGHijklMNOP')).toBe('abcd**************');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('masks a short key without exposing more than the prefix length', () => {
|
|
11
|
+
expect(maskCredential('ab')).toBe('ab**************');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns only asterisks for an empty string', () => {
|
|
15
|
+
expect(maskCredential('')).toBe('**************');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Number of leading characters shown for identification on credential display surfaces. */
|
|
2
|
+
export const CREDENTIAL_PREFIX_LENGTH = 4;
|
|
3
|
+
|
|
4
|
+
/** Fixed asterisk pad length for masked credentials (does not encode secret length). */
|
|
5
|
+
export const CREDENTIAL_MASK_PAD_LENGTH = 14;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Mask a credential for UI display: first {@link CREDENTIAL_PREFIX_LENGTH} characters
|
|
9
|
+
* plus a fixed asterisk pad. The full secret must never be returned on ordinary reads.
|
|
10
|
+
*/
|
|
11
|
+
export const maskCredential = (key: string): string => {
|
|
12
|
+
const prefix = key.slice(0, CREDENTIAL_PREFIX_LENGTH);
|
|
13
|
+
return `${prefix}${'*'.repeat(CREDENTIAL_MASK_PAD_LENGTH)}`;
|
|
14
|
+
};
|