@dialpad/dialtone-css 8.81.0-next.14 → 8.81.0-next.15
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/lib/build/js/dialtone_migrate/index.mjs +54 -7
- package/lib/build/js/dialtone_migrate/test.mjs +148 -0
- package/lib/dist/js/dialtone_migrate/index.mjs +54 -7
- package/lib/dist/js/dialtone_migrate/test.mjs +148 -0
- package/lib/dist/tokens/tokens-raincloud-dark.css +1050 -0
- package/lib/dist/tokens-docs.json +1 -1
- package/package.json +2 -2
- package/lib/dist/tokens/tokens-103-dark.css +0 -1050
- package/lib/dist/tokens/tokens-ceruleo-dark.css +0 -1050
- package/lib/dist/tokens/tokens-ceruleo-light.css +0 -1050
- /package/lib/dist/tokens/{tokens-131-dark.css → tokens-alpine-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-131-light.css → tokens-alpine-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-136-dark.css → tokens-arctic-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-136-light.css → tokens-arctic-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-118-dark.css → tokens-aurora-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-118-light.css → tokens-aurora-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-112-dark.css → tokens-autumn-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-112-light.css → tokens-autumn-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-110-dark.css → tokens-blue-hour-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-110-light.css → tokens-blue-hour-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-109-dark.css → tokens-brick-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-109-light.css → tokens-brick-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-117-dark.css → tokens-cactus-bloom-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-117-light.css → tokens-cactus-bloom-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-115-dark.css → tokens-cayenne-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-115-light.css → tokens-cayenne-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-106-dark.css → tokens-cedar-grove-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-106-light.css → tokens-cedar-grove-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-113-dark.css → tokens-cobalt-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-113-light.css → tokens-cobalt-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-116-dark.css → tokens-copper-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-116-light.css → tokens-copper-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-127-dark.css → tokens-coral-reef-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-127-light.css → tokens-coral-reef-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-121-dark.css → tokens-dragonfruit-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-121-light.css → tokens-dragonfruit-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-102-dark.css → tokens-eucalyptus-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-103-light.css → tokens-eucalyptus-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-105-dark.css → tokens-fjord-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-105-light.css → tokens-fjord-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-124-dark.css → tokens-inkberry-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-124-light.css → tokens-inkberry-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-114-dark.css → tokens-kiln-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-114-light.css → tokens-kiln-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-134-dark.css → tokens-lavender-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-134-light.css → tokens-lavender-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-133-dark.css → tokens-marigold-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-133-light.css → tokens-marigold-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-137-dark.css → tokens-mulberry-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-137-light.css → tokens-mulberry-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-104-dark.css → tokens-mushroom-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-104-light.css → tokens-mushroom-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-130-dark.css → tokens-nightshade-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-130-light.css → tokens-nightshade-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-101-dark.css → tokens-paprika-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-101-light.css → tokens-paprika-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-107-dark.css → tokens-peach-blossom-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-107-light.css → tokens-peach-blossom-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-108-dark.css → tokens-poppy-field-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-108-light.css → tokens-poppy-field-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-102-light.css → tokens-raincloud-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-123-dark.css → tokens-rhubarb-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-123-light.css → tokens-rhubarb-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-119-dark.css → tokens-rust-harbor-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-119-light.css → tokens-rust-harbor-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-128-dark.css → tokens-sea-glow-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-128-light.css → tokens-sea-glow-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-122-dark.css → tokens-seashell-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-122-light.css → tokens-seashell-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-129-dark.css → tokens-solstice-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-129-light.css → tokens-solstice-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-120-dark.css → tokens-storm-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-120-light.css → tokens-storm-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-135-dark.css → tokens-tropical-night-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-135-light.css → tokens-tropical-night-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-111-dark.css → tokens-wildflower-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-111-light.css → tokens-wildflower-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-132-dark.css → tokens-wineberry-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-132-light.css → tokens-wineberry-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-126-dark.css → tokens-winter-gold-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-126-light.css → tokens-winter-gold-light.css} +0 -0
- /package/lib/dist/tokens/{tokens-125-dark.css → tokens-woodland-dark.css} +0 -0
- /package/lib/dist/tokens/{tokens-125-light.css → tokens-woodland-light.css} +0 -0
|
@@ -56,11 +56,29 @@ const __dirname = path.dirname(__filename);
|
|
|
56
56
|
* @property {string} [scriptDir] - Directory name for standalone scripts
|
|
57
57
|
* @property {boolean} [supportsPackageArg] - Forward --package to this standalone script
|
|
58
58
|
* @property {string[]} [extraArgs] - Extra CLI args to forward
|
|
59
|
-
* @property {RegExp[]} detectPatterns - Patterns that indicate migration is
|
|
59
|
+
* @property {RegExp[]} detectPatterns - Patterns that unambiguously indicate migration is
|
|
60
|
+
* still needed. Always evaluated, regardless of skipIfPatterns.
|
|
61
|
+
* @property {RegExp[]} [ambiguousDetectPatterns] - Patterns that overlap with valid
|
|
62
|
+
* post-migration values. Only evaluated when skipIfPatterns does not match, so a file
|
|
63
|
+
* already containing a new-scale-only marker isn't flagged on these alone — but an
|
|
64
|
+
* unambiguous detectPatterns match elsewhere in the same (partially migrated) file
|
|
65
|
+
* still counts.
|
|
66
|
+
* @property {RegExp[]} [skipIfPatterns] - New-scale-only markers that gate
|
|
67
|
+
* ambiguousDetectPatterns — their presence means the file has already been migrated.
|
|
60
68
|
* @property {string[]} fileExtensions - File extensions to scan during health check
|
|
61
69
|
*/
|
|
62
70
|
|
|
63
71
|
/** @type {Migration[]} */
|
|
72
|
+
// Consumes a Dialtone tag's attribute soup, treating quoted attribute values as opaque so a
|
|
73
|
+
// literal > inside one (e.g. :disabled="count > 0") doesn't truncate the match early.
|
|
74
|
+
const DT_TAG_ATTRS = '(?:[^>"\']|"[^"]*"|\'[^\']*\')*';
|
|
75
|
+
|
|
76
|
+
// Allowlist (not denylist) of what may immediately precede "gap": whitespace (bare gap=),
|
|
77
|
+
// whitespace+":" (shorthand :gap=), or whitespace+"v-bind:" (long form). This rejects
|
|
78
|
+
// data-gap, @gap, v-on:gap, v-model:gap, v-slot:gap, and any other directive namespace,
|
|
79
|
+
// without having to enumerate every prefix that isn't the real gap prop/attribute.
|
|
80
|
+
const GAP_ATTR_LEAD = '(?:(?<=\\s)|(?<=\\s:)|(?<=\\sv-bind:))';
|
|
81
|
+
|
|
64
82
|
const MIGRATIONS = [
|
|
65
83
|
// ── Required (breaking) ────────────────────────────────────────────
|
|
66
84
|
{
|
|
@@ -162,9 +180,13 @@ const MIGRATIONS = [
|
|
|
162
180
|
type: 'standalone',
|
|
163
181
|
scriptDir: 'dialtone_migrate_props',
|
|
164
182
|
detectPatterns: [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
183
|
+
// (?<!-) blocks a hyphen immediately before the prop name — \b alone still matches
|
|
184
|
+
// there (- is a non-word char), which would treat native v-show as the show prop.
|
|
185
|
+
new RegExp(`<(?:dt-[\\w-]+|Dt\\w+)\\b${DT_TAG_ATTRS}\\b(?<!-)(?:show|hide-close|hide-icon|label-visible|selected-values)(?:=|[\\s>])`),
|
|
186
|
+
// (?<!-) blocks a hyphen immediately before "title" so data-title (and any other
|
|
187
|
+
// custom -title attribute) isn't mistaken for the title prop.
|
|
188
|
+
new RegExp(`<(?:dt-(?:banner|notice|toast|modal)|Dt(?:Banner|Notice|Toast|Modal))\\b${DT_TAG_ATTRS}\\b(?<!-)title(?:=|[\\s>])`),
|
|
189
|
+
new RegExp(`<(?:dt-[\\w-]+|Dt\\w+)\\b${DT_TAG_ATTRS}@(?:input|change)(?:=|\\.)`),
|
|
168
190
|
/kind="(?:danger|error)"/,
|
|
169
191
|
/validation-state="(?:error|success)"/,
|
|
170
192
|
],
|
|
@@ -242,9 +264,27 @@ const MIGRATIONS = [
|
|
|
242
264
|
category: 'opt-in',
|
|
243
265
|
type: 'config',
|
|
244
266
|
configName: 'stack-gap-to-spacing',
|
|
267
|
+
// Old-only stops (never valid post-migration values) — always flagged as pending.
|
|
245
268
|
detectPatterns: [
|
|
246
|
-
|
|
247
|
-
/d-stack--gap-(?:
|
|
269
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="(?:350|450|500|550|625|650|700)"`),
|
|
270
|
+
/d-stack--gap-(?:350|450|500|550|625|650|700)/,
|
|
271
|
+
],
|
|
272
|
+
// Old and new gap scales share some numeric stops (e.g. "100"), so a bare match on
|
|
273
|
+
// these doesn't prove pending work. Only counted when the file has no new-scale-only
|
|
274
|
+
// marker (skipIfPatterns) — an unambiguous detectPatterns match elsewhere in the same
|
|
275
|
+
// (partially migrated) file is still flagged regardless.
|
|
276
|
+
ambiguousDetectPatterns: [
|
|
277
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="(?:50|100|200|300|400|525|600)"`),
|
|
278
|
+
/d-stack--gap-(?:50|100|200|300|400|525|600)/,
|
|
279
|
+
],
|
|
280
|
+
// Mirrors dialtone_migration_helper/configs/stack-gap-to-spacing.mjs's isAlreadyMigrated
|
|
281
|
+
// check: new-scale-only stops indicate the file was already migrated.
|
|
282
|
+
skipIfPatterns: [
|
|
283
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="(?:25|75|150|250|525)"`),
|
|
284
|
+
new RegExp(`${GAP_ATTR_LEAD}gap='(?:25|75|150|250|525)'`),
|
|
285
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="'(?:25|75|150|250|525)'"`),
|
|
286
|
+
/d-stack--gap-(?:25|75|150|250|525)/,
|
|
287
|
+
/d-description-list--gap-(?:25|75|150|250|525)/,
|
|
248
288
|
],
|
|
249
289
|
fileExtensions: ['.vue', '.html', '.js', '.ts', '.jsx', '.tsx', '.md'],
|
|
250
290
|
},
|
|
@@ -546,7 +586,14 @@ async function healthCheck (cwd) {
|
|
|
546
586
|
|
|
547
587
|
for (const [file, content] of fileContents) {
|
|
548
588
|
if (!migration.fileExtensions.some(ext => file.endsWith(ext))) continue;
|
|
549
|
-
|
|
589
|
+
|
|
590
|
+
let patterns = migration.detectPatterns;
|
|
591
|
+
const alreadyMigrated = migration.skipIfPatterns?.some(pattern => pattern.test(content));
|
|
592
|
+
if (!alreadyMigrated && migration.ambiguousDetectPatterns) {
|
|
593
|
+
patterns = [...patterns, ...migration.ambiguousDetectPatterns];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for (const pattern of patterns) {
|
|
550
597
|
// Reset lastIndex for global patterns
|
|
551
598
|
const re = new RegExp(pattern.source, pattern.flags.replace('g', ''));
|
|
552
599
|
if (re.test(content)) {
|
|
@@ -86,3 +86,151 @@ describe('--package validation', () => {
|
|
|
86
86
|
assert.equal(status, 0);
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
describe('--health-check', () => {
|
|
91
|
+
function runHealthCheck (files) {
|
|
92
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dlt-health-'));
|
|
93
|
+
try {
|
|
94
|
+
for (const [name, contents] of Object.entries(files)) {
|
|
95
|
+
fs.writeFileSync(path.join(tmp, name), contents);
|
|
96
|
+
}
|
|
97
|
+
return execFileSync(process.execPath, [cli, '--health-check', '--cwd', tmp],
|
|
98
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
99
|
+
} finally {
|
|
100
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
it('does not flag native v-show on non-Dialtone elements as component-props', () => {
|
|
105
|
+
const output = runHealthCheck({
|
|
106
|
+
'App.vue': '<template><div v-show="isReady">Ready</div></template>',
|
|
107
|
+
});
|
|
108
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('does not flag native v-show on a Dialtone element as component-props', () => {
|
|
112
|
+
const output = runHealthCheck({
|
|
113
|
+
'App.vue': '<template><dt-modal v-show="isOpen"></dt-modal></template>',
|
|
114
|
+
});
|
|
115
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('does not flag v-show on dt-button as component-props', () => {
|
|
119
|
+
const output = runHealthCheck({
|
|
120
|
+
'App.vue': '<template><dt-button v-show="visible">Go</dt-button></template>',
|
|
121
|
+
});
|
|
122
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('does not flag data-title or v-title on a Dialtone element as component-props', () => {
|
|
126
|
+
const output = runHealthCheck({
|
|
127
|
+
'App.vue': '<template><dt-modal data-title="tooltip" v-title="x"></dt-modal></template>',
|
|
128
|
+
});
|
|
129
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('still flags show= on a Dialtone component as component-props', () => {
|
|
133
|
+
const output = runHealthCheck({
|
|
134
|
+
'App.vue': '<template><dt-modal show="isOpen"></dt-modal></template>',
|
|
135
|
+
});
|
|
136
|
+
assert.match(output, /\[PENDING\].*Component Props & Events \(component-props\)/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('still flags title= on a Dialtone component as component-props', () => {
|
|
140
|
+
const output = runHealthCheck({
|
|
141
|
+
'App.vue': '<template><dt-modal title="isOpen"></dt-modal></template>',
|
|
142
|
+
});
|
|
143
|
+
assert.match(output, /\[PENDING\].*Component Props & Events \(component-props\)/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('still flags :show= on a Dialtone component when a quoted attribute earlier in the tag contains a literal >', () => {
|
|
147
|
+
const output = runHealthCheck({
|
|
148
|
+
'App.vue': '<template><dt-modal :disabled="count > 0" :show="isOpen"></dt-modal></template>',
|
|
149
|
+
});
|
|
150
|
+
assert.match(output, /\[PENDING\].*Component Props & Events \(component-props\)/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('does not flag an already-migrated file for stack-gap-to-spacing', () => {
|
|
154
|
+
const output = runHealthCheck({
|
|
155
|
+
'App.vue': '<template><dt-stack gap="100"></dt-stack><dt-stack gap="25"></dt-stack></template>',
|
|
156
|
+
});
|
|
157
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('still flags an unmigrated file for stack-gap-to-spacing', () => {
|
|
161
|
+
const output = runHealthCheck({
|
|
162
|
+
'App.vue': '<template><dt-stack gap="625"></dt-stack></template>',
|
|
163
|
+
});
|
|
164
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('does not flag an unrelated data-gap attribute for stack-gap-to-spacing', () => {
|
|
168
|
+
const output = runHealthCheck({
|
|
169
|
+
'App.vue': '<template><div data-gap="625"></div></template>',
|
|
170
|
+
});
|
|
171
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('does not let an unrelated data-gap new-scale value suppress a real ambiguous pending gap', () => {
|
|
175
|
+
const output = runHealthCheck({
|
|
176
|
+
'App.vue': '<template><div data-gap="25"></div><dt-stack gap="100"></dt-stack></template>',
|
|
177
|
+
});
|
|
178
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('does not flag an @gap event-listener value as a pending gap prop', () => {
|
|
182
|
+
const output = runHealthCheck({
|
|
183
|
+
'App.vue': '<template><dt-stack @gap="625"></dt-stack></template>',
|
|
184
|
+
});
|
|
185
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('does not let an @gap or v-on:gap event-listener value suppress a real ambiguous pending gap', () => {
|
|
189
|
+
const output = runHealthCheck({
|
|
190
|
+
'App.vue': '<template><dt-stack @gap="25"></dt-stack><dt-stack v-on:gap="75"></dt-stack><dt-stack gap="100"></dt-stack></template>',
|
|
191
|
+
});
|
|
192
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('still flags a bound :gap= and v-bind:gap= as pending', () => {
|
|
196
|
+
const output = runHealthCheck({
|
|
197
|
+
'App.vue': '<template><dt-stack :gap="625"></dt-stack><dt-stack v-bind:gap="625"></dt-stack></template>',
|
|
198
|
+
});
|
|
199
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does not flag v-model:gap and still detects a real ambiguous gap alongside it', () => {
|
|
203
|
+
const output = runHealthCheck({
|
|
204
|
+
'App.vue': '<template><dt-stack v-model:gap="someRef"></dt-stack><dt-stack gap="100"></dt-stack></template>',
|
|
205
|
+
});
|
|
206
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('does not flag gap="525" for stack-gap-to-spacing', () => {
|
|
210
|
+
const output = runHealthCheck({
|
|
211
|
+
'App.vue': '<template><dt-stack gap="525"></dt-stack></template>',
|
|
212
|
+
});
|
|
213
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('does not flag a mix of single-quoted gap=\'25\' and double-quoted gap="100" for stack-gap-to-spacing', () => {
|
|
217
|
+
const output = runHealthCheck({
|
|
218
|
+
'App.vue': '<template><dt-stack gap=\'25\'></dt-stack><dt-stack gap="100"></dt-stack></template>',
|
|
219
|
+
});
|
|
220
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('still flags an unmigrated stop in a partially migrated (mixed) file for stack-gap-to-spacing', () => {
|
|
224
|
+
const output = runHealthCheck({
|
|
225
|
+
'App.vue': '<template><dt-stack gap="25"></dt-stack><dt-stack gap="625"></dt-stack></template>',
|
|
226
|
+
});
|
|
227
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('does not flag an old stack class alongside a bound gap="\'25\'" for stack-gap-to-spacing', () => {
|
|
231
|
+
const output = runHealthCheck({
|
|
232
|
+
'App.vue': '<template><dt-stack class="d-stack--gap-100" :gap="\'25\'"></dt-stack></template>',
|
|
233
|
+
});
|
|
234
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -56,11 +56,29 @@ const __dirname = path.dirname(__filename);
|
|
|
56
56
|
* @property {string} [scriptDir] - Directory name for standalone scripts
|
|
57
57
|
* @property {boolean} [supportsPackageArg] - Forward --package to this standalone script
|
|
58
58
|
* @property {string[]} [extraArgs] - Extra CLI args to forward
|
|
59
|
-
* @property {RegExp[]} detectPatterns - Patterns that indicate migration is
|
|
59
|
+
* @property {RegExp[]} detectPatterns - Patterns that unambiguously indicate migration is
|
|
60
|
+
* still needed. Always evaluated, regardless of skipIfPatterns.
|
|
61
|
+
* @property {RegExp[]} [ambiguousDetectPatterns] - Patterns that overlap with valid
|
|
62
|
+
* post-migration values. Only evaluated when skipIfPatterns does not match, so a file
|
|
63
|
+
* already containing a new-scale-only marker isn't flagged on these alone — but an
|
|
64
|
+
* unambiguous detectPatterns match elsewhere in the same (partially migrated) file
|
|
65
|
+
* still counts.
|
|
66
|
+
* @property {RegExp[]} [skipIfPatterns] - New-scale-only markers that gate
|
|
67
|
+
* ambiguousDetectPatterns — their presence means the file has already been migrated.
|
|
60
68
|
* @property {string[]} fileExtensions - File extensions to scan during health check
|
|
61
69
|
*/
|
|
62
70
|
|
|
63
71
|
/** @type {Migration[]} */
|
|
72
|
+
// Consumes a Dialtone tag's attribute soup, treating quoted attribute values as opaque so a
|
|
73
|
+
// literal > inside one (e.g. :disabled="count > 0") doesn't truncate the match early.
|
|
74
|
+
const DT_TAG_ATTRS = '(?:[^>"\']|"[^"]*"|\'[^\']*\')*';
|
|
75
|
+
|
|
76
|
+
// Allowlist (not denylist) of what may immediately precede "gap": whitespace (bare gap=),
|
|
77
|
+
// whitespace+":" (shorthand :gap=), or whitespace+"v-bind:" (long form). This rejects
|
|
78
|
+
// data-gap, @gap, v-on:gap, v-model:gap, v-slot:gap, and any other directive namespace,
|
|
79
|
+
// without having to enumerate every prefix that isn't the real gap prop/attribute.
|
|
80
|
+
const GAP_ATTR_LEAD = '(?:(?<=\\s)|(?<=\\s:)|(?<=\\sv-bind:))';
|
|
81
|
+
|
|
64
82
|
const MIGRATIONS = [
|
|
65
83
|
// ── Required (breaking) ────────────────────────────────────────────
|
|
66
84
|
{
|
|
@@ -162,9 +180,13 @@ const MIGRATIONS = [
|
|
|
162
180
|
type: 'standalone',
|
|
163
181
|
scriptDir: 'dialtone_migrate_props',
|
|
164
182
|
detectPatterns: [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
183
|
+
// (?<!-) blocks a hyphen immediately before the prop name — \b alone still matches
|
|
184
|
+
// there (- is a non-word char), which would treat native v-show as the show prop.
|
|
185
|
+
new RegExp(`<(?:dt-[\\w-]+|Dt\\w+)\\b${DT_TAG_ATTRS}\\b(?<!-)(?:show|hide-close|hide-icon|label-visible|selected-values)(?:=|[\\s>])`),
|
|
186
|
+
// (?<!-) blocks a hyphen immediately before "title" so data-title (and any other
|
|
187
|
+
// custom -title attribute) isn't mistaken for the title prop.
|
|
188
|
+
new RegExp(`<(?:dt-(?:banner|notice|toast|modal)|Dt(?:Banner|Notice|Toast|Modal))\\b${DT_TAG_ATTRS}\\b(?<!-)title(?:=|[\\s>])`),
|
|
189
|
+
new RegExp(`<(?:dt-[\\w-]+|Dt\\w+)\\b${DT_TAG_ATTRS}@(?:input|change)(?:=|\\.)`),
|
|
168
190
|
/kind="(?:danger|error)"/,
|
|
169
191
|
/validation-state="(?:error|success)"/,
|
|
170
192
|
],
|
|
@@ -242,9 +264,27 @@ const MIGRATIONS = [
|
|
|
242
264
|
category: 'opt-in',
|
|
243
265
|
type: 'config',
|
|
244
266
|
configName: 'stack-gap-to-spacing',
|
|
267
|
+
// Old-only stops (never valid post-migration values) — always flagged as pending.
|
|
245
268
|
detectPatterns: [
|
|
246
|
-
|
|
247
|
-
/d-stack--gap-(?:
|
|
269
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="(?:350|450|500|550|625|650|700)"`),
|
|
270
|
+
/d-stack--gap-(?:350|450|500|550|625|650|700)/,
|
|
271
|
+
],
|
|
272
|
+
// Old and new gap scales share some numeric stops (e.g. "100"), so a bare match on
|
|
273
|
+
// these doesn't prove pending work. Only counted when the file has no new-scale-only
|
|
274
|
+
// marker (skipIfPatterns) — an unambiguous detectPatterns match elsewhere in the same
|
|
275
|
+
// (partially migrated) file is still flagged regardless.
|
|
276
|
+
ambiguousDetectPatterns: [
|
|
277
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="(?:50|100|200|300|400|525|600)"`),
|
|
278
|
+
/d-stack--gap-(?:50|100|200|300|400|525|600)/,
|
|
279
|
+
],
|
|
280
|
+
// Mirrors dialtone_migration_helper/configs/stack-gap-to-spacing.mjs's isAlreadyMigrated
|
|
281
|
+
// check: new-scale-only stops indicate the file was already migrated.
|
|
282
|
+
skipIfPatterns: [
|
|
283
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="(?:25|75|150|250|525)"`),
|
|
284
|
+
new RegExp(`${GAP_ATTR_LEAD}gap='(?:25|75|150|250|525)'`),
|
|
285
|
+
new RegExp(`${GAP_ATTR_LEAD}gap="'(?:25|75|150|250|525)'"`),
|
|
286
|
+
/d-stack--gap-(?:25|75|150|250|525)/,
|
|
287
|
+
/d-description-list--gap-(?:25|75|150|250|525)/,
|
|
248
288
|
],
|
|
249
289
|
fileExtensions: ['.vue', '.html', '.js', '.ts', '.jsx', '.tsx', '.md'],
|
|
250
290
|
},
|
|
@@ -546,7 +586,14 @@ async function healthCheck (cwd) {
|
|
|
546
586
|
|
|
547
587
|
for (const [file, content] of fileContents) {
|
|
548
588
|
if (!migration.fileExtensions.some(ext => file.endsWith(ext))) continue;
|
|
549
|
-
|
|
589
|
+
|
|
590
|
+
let patterns = migration.detectPatterns;
|
|
591
|
+
const alreadyMigrated = migration.skipIfPatterns?.some(pattern => pattern.test(content));
|
|
592
|
+
if (!alreadyMigrated && migration.ambiguousDetectPatterns) {
|
|
593
|
+
patterns = [...patterns, ...migration.ambiguousDetectPatterns];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for (const pattern of patterns) {
|
|
550
597
|
// Reset lastIndex for global patterns
|
|
551
598
|
const re = new RegExp(pattern.source, pattern.flags.replace('g', ''));
|
|
552
599
|
if (re.test(content)) {
|
|
@@ -86,3 +86,151 @@ describe('--package validation', () => {
|
|
|
86
86
|
assert.equal(status, 0);
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
describe('--health-check', () => {
|
|
91
|
+
function runHealthCheck (files) {
|
|
92
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dlt-health-'));
|
|
93
|
+
try {
|
|
94
|
+
for (const [name, contents] of Object.entries(files)) {
|
|
95
|
+
fs.writeFileSync(path.join(tmp, name), contents);
|
|
96
|
+
}
|
|
97
|
+
return execFileSync(process.execPath, [cli, '--health-check', '--cwd', tmp],
|
|
98
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
99
|
+
} finally {
|
|
100
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
it('does not flag native v-show on non-Dialtone elements as component-props', () => {
|
|
105
|
+
const output = runHealthCheck({
|
|
106
|
+
'App.vue': '<template><div v-show="isReady">Ready</div></template>',
|
|
107
|
+
});
|
|
108
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('does not flag native v-show on a Dialtone element as component-props', () => {
|
|
112
|
+
const output = runHealthCheck({
|
|
113
|
+
'App.vue': '<template><dt-modal v-show="isOpen"></dt-modal></template>',
|
|
114
|
+
});
|
|
115
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('does not flag v-show on dt-button as component-props', () => {
|
|
119
|
+
const output = runHealthCheck({
|
|
120
|
+
'App.vue': '<template><dt-button v-show="visible">Go</dt-button></template>',
|
|
121
|
+
});
|
|
122
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('does not flag data-title or v-title on a Dialtone element as component-props', () => {
|
|
126
|
+
const output = runHealthCheck({
|
|
127
|
+
'App.vue': '<template><dt-modal data-title="tooltip" v-title="x"></dt-modal></template>',
|
|
128
|
+
});
|
|
129
|
+
assert.match(output, /\[DONE\].*Component Props & Events \(component-props\)/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('still flags show= on a Dialtone component as component-props', () => {
|
|
133
|
+
const output = runHealthCheck({
|
|
134
|
+
'App.vue': '<template><dt-modal show="isOpen"></dt-modal></template>',
|
|
135
|
+
});
|
|
136
|
+
assert.match(output, /\[PENDING\].*Component Props & Events \(component-props\)/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('still flags title= on a Dialtone component as component-props', () => {
|
|
140
|
+
const output = runHealthCheck({
|
|
141
|
+
'App.vue': '<template><dt-modal title="isOpen"></dt-modal></template>',
|
|
142
|
+
});
|
|
143
|
+
assert.match(output, /\[PENDING\].*Component Props & Events \(component-props\)/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('still flags :show= on a Dialtone component when a quoted attribute earlier in the tag contains a literal >', () => {
|
|
147
|
+
const output = runHealthCheck({
|
|
148
|
+
'App.vue': '<template><dt-modal :disabled="count > 0" :show="isOpen"></dt-modal></template>',
|
|
149
|
+
});
|
|
150
|
+
assert.match(output, /\[PENDING\].*Component Props & Events \(component-props\)/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('does not flag an already-migrated file for stack-gap-to-spacing', () => {
|
|
154
|
+
const output = runHealthCheck({
|
|
155
|
+
'App.vue': '<template><dt-stack gap="100"></dt-stack><dt-stack gap="25"></dt-stack></template>',
|
|
156
|
+
});
|
|
157
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('still flags an unmigrated file for stack-gap-to-spacing', () => {
|
|
161
|
+
const output = runHealthCheck({
|
|
162
|
+
'App.vue': '<template><dt-stack gap="625"></dt-stack></template>',
|
|
163
|
+
});
|
|
164
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('does not flag an unrelated data-gap attribute for stack-gap-to-spacing', () => {
|
|
168
|
+
const output = runHealthCheck({
|
|
169
|
+
'App.vue': '<template><div data-gap="625"></div></template>',
|
|
170
|
+
});
|
|
171
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('does not let an unrelated data-gap new-scale value suppress a real ambiguous pending gap', () => {
|
|
175
|
+
const output = runHealthCheck({
|
|
176
|
+
'App.vue': '<template><div data-gap="25"></div><dt-stack gap="100"></dt-stack></template>',
|
|
177
|
+
});
|
|
178
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('does not flag an @gap event-listener value as a pending gap prop', () => {
|
|
182
|
+
const output = runHealthCheck({
|
|
183
|
+
'App.vue': '<template><dt-stack @gap="625"></dt-stack></template>',
|
|
184
|
+
});
|
|
185
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('does not let an @gap or v-on:gap event-listener value suppress a real ambiguous pending gap', () => {
|
|
189
|
+
const output = runHealthCheck({
|
|
190
|
+
'App.vue': '<template><dt-stack @gap="25"></dt-stack><dt-stack v-on:gap="75"></dt-stack><dt-stack gap="100"></dt-stack></template>',
|
|
191
|
+
});
|
|
192
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('still flags a bound :gap= and v-bind:gap= as pending', () => {
|
|
196
|
+
const output = runHealthCheck({
|
|
197
|
+
'App.vue': '<template><dt-stack :gap="625"></dt-stack><dt-stack v-bind:gap="625"></dt-stack></template>',
|
|
198
|
+
});
|
|
199
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does not flag v-model:gap and still detects a real ambiguous gap alongside it', () => {
|
|
203
|
+
const output = runHealthCheck({
|
|
204
|
+
'App.vue': '<template><dt-stack v-model:gap="someRef"></dt-stack><dt-stack gap="100"></dt-stack></template>',
|
|
205
|
+
});
|
|
206
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('does not flag gap="525" for stack-gap-to-spacing', () => {
|
|
210
|
+
const output = runHealthCheck({
|
|
211
|
+
'App.vue': '<template><dt-stack gap="525"></dt-stack></template>',
|
|
212
|
+
});
|
|
213
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('does not flag a mix of single-quoted gap=\'25\' and double-quoted gap="100" for stack-gap-to-spacing', () => {
|
|
217
|
+
const output = runHealthCheck({
|
|
218
|
+
'App.vue': '<template><dt-stack gap=\'25\'></dt-stack><dt-stack gap="100"></dt-stack></template>',
|
|
219
|
+
});
|
|
220
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('still flags an unmigrated stop in a partially migrated (mixed) file for stack-gap-to-spacing', () => {
|
|
224
|
+
const output = runHealthCheck({
|
|
225
|
+
'App.vue': '<template><dt-stack gap="25"></dt-stack><dt-stack gap="625"></dt-stack></template>',
|
|
226
|
+
});
|
|
227
|
+
assert.match(output, /\[PENDING\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('does not flag an old stack class alongside a bound gap="\'25\'" for stack-gap-to-spacing', () => {
|
|
231
|
+
const output = runHealthCheck({
|
|
232
|
+
'App.vue': '<template><dt-stack class="d-stack--gap-100" :gap="\'25\'"></dt-stack></template>',
|
|
233
|
+
});
|
|
234
|
+
assert.match(output, /\[DONE\].*Stack Gap to Spacing \(stack-gap-to-spacing\)/);
|
|
235
|
+
});
|
|
236
|
+
});
|