@ethlete/components 1.0.0-next.63 ā 1.0.0-next.65
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/CHANGELOG.md +336 -0
- package/fesm2022/ethlete-components.mjs +7698 -4988
- package/fesm2022/ethlete-components.mjs.map +1 -1
- package/generators/generators.json +40 -0
- package/generators/migrate-bracket-default-cards/bracket-default-cards.js +67 -0
- package/generators/migrate-bracket-default-cards/migration.js +63 -0
- package/generators/migrate-bracket-default-cards/schema.json +26 -0
- package/generators/migrate-bracket-ethlete-renames/bracket-ethlete-renames.js +15 -0
- package/generators/migrate-bracket-ethlete-renames/migration.js +31 -0
- package/generators/migrate-bracket-ethlete-renames/schema.json +26 -0
- package/generators/migrate-chart-baseline-y/chart-baseline-y.js +10 -0
- package/generators/migrate-chart-baseline-y/migration.js +41 -0
- package/generators/migrate-chart-baseline-y/schema.json +26 -0
- package/generators/migrate-grid-engine-internals/grid-engine-internals.js +43 -0
- package/generators/migrate-grid-engine-internals/migration.js +11 -0
- package/generators/migrate-grid-engine-internals/schema.json +26 -0
- package/generators/migrate-overlay-fullscreen-animation/migration.js +11 -0
- package/generators/migrate-overlay-fullscreen-animation/overlay-fullscreen-animation.js +16 -0
- package/generators/migrate-overlay-fullscreen-animation/schema.json +26 -0
- package/generators/migrate-overlay-nav-tab-imports/migration.js +35 -0
- package/generators/migrate-overlay-nav-tab-imports/overlay-nav-tab-imports.js +25 -0
- package/generators/migrate-overlay-nav-tab-imports/schema.json +26 -0
- package/generators/migrate-stream-default-components/migration.js +86 -0
- package/generators/migrate-stream-default-components/schema.json +26 -0
- package/generators/migrate-stream-default-components/stream-default-components.js +111 -0
- package/generators/migrate-youtube-player-slot-directive/migration.js +11 -0
- package/generators/migrate-youtube-player-slot-directive/schema.json +26 -0
- package/generators/migrate-youtube-player-slot-directive/youtube-player-slot-directive.js +12 -0
- package/generators/removed-exports/removed-exports.js +154 -0
- package/generators/removed-exports/run-removed-exports-migration.js +48 -0
- package/migrations.json +64 -0
- package/package.json +2 -2
- package/types/ethlete-components.d.ts +3768 -3269
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { formatFiles, joinPathFragments } from '@nx/devkit';
|
|
2
|
+
import { dirname } from 'node:path/posix';
|
|
3
|
+
import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
|
|
4
|
+
import { addStreamConfigToAppConfig, addStreamOverlayImportsToFile, streamUsageTask, scanStreamDefaultComponentsInFile, TEMPLATE_URL, } from './stream-default-components.js';
|
|
5
|
+
export const STREAM_DEFAULT_COMPONENTS_REPORT_PATH = 'stream-default-components-migration-tasks.md';
|
|
6
|
+
const renderReport = (tasks) => [
|
|
7
|
+
'# Stream default components migration tasks',
|
|
8
|
+
'',
|
|
9
|
+
'The shipped loading and error overlays are opt-in. A stream slot whose config names none renders no',
|
|
10
|
+
'overlay while the player loads or fails. The migration spread `STREAM_DEFAULT_COMPONENTS` into every literal',
|
|
11
|
+
'`provideStreamConfig` call, and added one to each application config of an app without any. The sites below',
|
|
12
|
+
'need it by hand. Add once, app-wide or where the slots render:',
|
|
13
|
+
'',
|
|
14
|
+
'```ts',
|
|
15
|
+
"import { STREAM_DEFAULT_COMPONENTS, provideStreamConfig } from '@ethlete/components';",
|
|
16
|
+
'',
|
|
17
|
+
'provideStreamConfig({ ...STREAM_DEFAULT_COMPONENTS });',
|
|
18
|
+
'```',
|
|
19
|
+
'',
|
|
20
|
+
'A stream that names overlays of its own needs nothing. Delete this file when done.',
|
|
21
|
+
'',
|
|
22
|
+
...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}:${task.line}`, `- ${task.message}`, ''].join('\n')),
|
|
23
|
+
].join('\n');
|
|
24
|
+
export default async function migrateStreamDefaultComponents(tree, schema) {
|
|
25
|
+
console.log('\nš Spreading STREAM_DEFAULT_COMPONENTS into provideStreamConfig...');
|
|
26
|
+
const scope = createMigrationScope(tree, schema);
|
|
27
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
28
|
+
const changed = [];
|
|
29
|
+
const tasks = [];
|
|
30
|
+
const usages = [];
|
|
31
|
+
const appConfigs = [];
|
|
32
|
+
let hasConfig = false;
|
|
33
|
+
scope.visit(tree, (filePath) => {
|
|
34
|
+
if (!/\.(ts|html)$/.test(filePath) || filePath.endsWith('.d.ts'))
|
|
35
|
+
return;
|
|
36
|
+
const content = tree.read(filePath, 'utf-8');
|
|
37
|
+
if (!content)
|
|
38
|
+
return;
|
|
39
|
+
const result = scanStreamDefaultComponentsInFile(filePath, content);
|
|
40
|
+
hasConfig ||= result.hasConfig;
|
|
41
|
+
tasks.push(...result.tasks);
|
|
42
|
+
if (result.usesStream)
|
|
43
|
+
usages.push(streamUsageTask(filePath, content));
|
|
44
|
+
if (filePath.endsWith('.ts') && addStreamConfigToAppConfig(content) !== null)
|
|
45
|
+
appConfigs.push(filePath);
|
|
46
|
+
let next = result.next;
|
|
47
|
+
if (filePath.endsWith('.ts') && content.includes('STREAM_IMPORTS')) {
|
|
48
|
+
const templates = [...content.matchAll(TEMPLATE_URL)]
|
|
49
|
+
.map((match) => tree.read(joinPathFragments(dirname(filePath), match[1] ?? ''), 'utf-8'))
|
|
50
|
+
.filter((template) => template !== null);
|
|
51
|
+
next = addStreamOverlayImportsToFile(next ?? content, templates) ?? next;
|
|
52
|
+
}
|
|
53
|
+
if (next !== null) {
|
|
54
|
+
tree.write(filePath, next);
|
|
55
|
+
changed.push(filePath);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (!hasConfig && usages.length > 0) {
|
|
59
|
+
for (const filePath of appConfigs) {
|
|
60
|
+
const next = addStreamConfigToAppConfig(tree.read(filePath, 'utf-8') ?? '');
|
|
61
|
+
if (next === null)
|
|
62
|
+
continue;
|
|
63
|
+
tree.write(filePath, next);
|
|
64
|
+
if (!changed.includes(filePath))
|
|
65
|
+
changed.push(filePath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const addedConfig = !hasConfig && usages.length > 0 && appConfigs.length > 0;
|
|
69
|
+
const report = hasConfig || addedConfig ? tasks : [...tasks, ...usages];
|
|
70
|
+
if (report.length > 0) {
|
|
71
|
+
tree.write(STREAM_DEFAULT_COMPONENTS_REPORT_PATH, renderReport(report));
|
|
72
|
+
}
|
|
73
|
+
if (!schema.skipFormat) {
|
|
74
|
+
await formatFiles(tree);
|
|
75
|
+
}
|
|
76
|
+
if (changed.length > 0) {
|
|
77
|
+
console.log(`\nā
Spread STREAM_DEFAULT_COMPONENTS in ${changed.length} file(s).`);
|
|
78
|
+
}
|
|
79
|
+
if (report.length > 0) {
|
|
80
|
+
console.log(`\nā ļø ${report.length} site(s) need the default components by hand ā see ${STREAM_DEFAULT_COMPONENTS_REPORT_PATH}.`);
|
|
81
|
+
}
|
|
82
|
+
else if (changed.length === 0) {
|
|
83
|
+
console.log('\nā
Nothing to do.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-stream-default-components",
|
|
4
|
+
"title": "Spread STREAM_DEFAULT_COMPONENTS into provideStreamConfig and report stream slots without one",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to scan. Defaults to every project in the workspace.",
|
|
11
|
+
"alias": "project"
|
|
12
|
+
},
|
|
13
|
+
"include": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" },
|
|
16
|
+
"description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
19
|
+
"skipFormat": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Skip formatting files after migration",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const USAGE_MARKERS = [/<et-[a-z]+-player-slot[\s/>]/, /\bSTREAM_(?:[A-Z]+_)?IMPORTS\b/];
|
|
2
|
+
const COMPONENTS_IMPORT = /import\s*\{([^}]*)\}\s*from\s*['"]@ethlete\/components['"]/g;
|
|
3
|
+
const lineOf = (content, index) => content.slice(0, index).split('\n').length;
|
|
4
|
+
const taskId = (filePath, line) => `stream-default-components--${filePath.replace(/[^a-zA-Z0-9]+/g, '-')}-${line}`;
|
|
5
|
+
/**
|
|
6
|
+
* Spreads `STREAM_DEFAULT_COMPONENTS` first into every literal `provideStreamConfig` call, so an overlay the
|
|
7
|
+
* config names itself still wins, and reports each call whose config is not a literal.
|
|
8
|
+
*/
|
|
9
|
+
export const scanStreamDefaultComponentsInFile = (filePath, content) => {
|
|
10
|
+
const usesStream = USAGE_MARKERS.some((marker) => marker.test(content));
|
|
11
|
+
const hasConfig = content.includes('provideStreamConfig(');
|
|
12
|
+
const tasks = [];
|
|
13
|
+
const importStatement = [...content.matchAll(COMPONENTS_IMPORT)].find((match) => /\bprovideStreamConfig\b/.test(match[1] ?? ''));
|
|
14
|
+
if (!importStatement || content.includes('STREAM_DEFAULT_COMPONENTS')) {
|
|
15
|
+
return { next: null, usesStream, hasConfig, tasks };
|
|
16
|
+
}
|
|
17
|
+
let rewrote = false;
|
|
18
|
+
const next = content.replace(/provideStreamConfig\(\s*(\{\s*\}|\{\s*|\)|[^\s{)])/g, (call, opening, index) => {
|
|
19
|
+
if (opening === ')') {
|
|
20
|
+
rewrote = true;
|
|
21
|
+
return 'provideStreamConfig({ ...STREAM_DEFAULT_COMPONENTS })';
|
|
22
|
+
}
|
|
23
|
+
if (opening.startsWith('{')) {
|
|
24
|
+
rewrote = true;
|
|
25
|
+
return opening.endsWith('}')
|
|
26
|
+
? 'provideStreamConfig({ ...STREAM_DEFAULT_COMPONENTS }'
|
|
27
|
+
: 'provideStreamConfig({ ...STREAM_DEFAULT_COMPONENTS, ';
|
|
28
|
+
}
|
|
29
|
+
const line = lineOf(content, index);
|
|
30
|
+
const argument = content.slice(index + call.length - 1).match(/^[^)\n]*/)?.[0] ?? '';
|
|
31
|
+
tasks.push({
|
|
32
|
+
id: taskId(filePath, line),
|
|
33
|
+
file: filePath,
|
|
34
|
+
line,
|
|
35
|
+
message: `\`provideStreamConfig(${argument})\` is not a literal the migration can edit. Spread \`STREAM_DEFAULT_COMPONENTS\` into it unless it names both overlays itself.`,
|
|
36
|
+
});
|
|
37
|
+
return call;
|
|
38
|
+
});
|
|
39
|
+
if (!rewrote)
|
|
40
|
+
return { next: null, usesStream, hasConfig, tasks };
|
|
41
|
+
const rewrittenImport = importStatement[0].replace('{', '{ STREAM_DEFAULT_COMPONENTS,');
|
|
42
|
+
const importIndex = next.indexOf(importStatement[0]);
|
|
43
|
+
return {
|
|
44
|
+
next: next.slice(0, importIndex) + rewrittenImport + next.slice(importIndex + importStatement[0].length),
|
|
45
|
+
usesStream,
|
|
46
|
+
hasConfig,
|
|
47
|
+
tasks,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export const streamUsageTask = (filePath, content) => {
|
|
51
|
+
const index = Math.min(...USAGE_MARKERS.map((marker) => content.search(marker)).filter((position) => position >= 0));
|
|
52
|
+
const line = lineOf(content, index);
|
|
53
|
+
return {
|
|
54
|
+
id: taskId(filePath, line),
|
|
55
|
+
file: filePath,
|
|
56
|
+
line,
|
|
57
|
+
message: 'Renders a stream player slot, but the app has no `provideStreamConfig`, so no loading or error overlay is registered.',
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export const TEMPLATE_URL = /templateUrl\s*:\s*['"]([^'"]+)['"]/g;
|
|
61
|
+
const STREAM_IMPORTS_NAME = /\bSTREAM_IMPORTS\b/;
|
|
62
|
+
const OVERLAY_SELECTORS = [
|
|
63
|
+
{ selector: '<et-stream-player-loading', component: 'StreamPlayerLoadingComponent' },
|
|
64
|
+
{ selector: '<et-stream-player-error', component: 'StreamPlayerErrorComponent' },
|
|
65
|
+
];
|
|
66
|
+
/**
|
|
67
|
+
* Adds the loading and error overlay components next to every `STREAM_IMPORTS` in a file whose templates
|
|
68
|
+
* render their selectors, or returns `null` when nothing needs adding.
|
|
69
|
+
*/
|
|
70
|
+
export const addStreamOverlayImportsToFile = (content, externalTemplates) => {
|
|
71
|
+
const importStatement = [...content.matchAll(COMPONENTS_IMPORT)].find((match) => STREAM_IMPORTS_NAME.test(match[1] ?? '') && !/\bSTREAM_IMPORTS\s+as\b/.test(match[1] ?? ''));
|
|
72
|
+
if (!importStatement || importStatement.index === undefined)
|
|
73
|
+
return null;
|
|
74
|
+
const templates = [content, ...externalTemplates];
|
|
75
|
+
const missing = OVERLAY_SELECTORS.filter(({ selector, component }) => templates.some((template) => template.includes(selector)) &&
|
|
76
|
+
!new RegExp(`\\b${component}\\b`).test(importStatement[1] ?? '')).map(({ component }) => component);
|
|
77
|
+
if (missing.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
const extra = missing.join(', ');
|
|
80
|
+
const start = importStatement.index;
|
|
81
|
+
const end = start + importStatement[0].length;
|
|
82
|
+
const rewrittenImport = importStatement[0].replace(STREAM_IMPORTS_NAME, `STREAM_IMPORTS, ${extra}`);
|
|
83
|
+
const addNextToUsage = (code) => code.replace(/(\.\.\.)?\bSTREAM_IMPORTS\b/g, (_, spread) => spread ? `...STREAM_IMPORTS, ${extra}` : `STREAM_IMPORTS, ${extra}`);
|
|
84
|
+
return addNextToUsage(content.slice(0, start)) + rewrittenImport + addNextToUsage(content.slice(end));
|
|
85
|
+
};
|
|
86
|
+
const APP_CONFIG_MARKER = /\bApplicationConfig\b|\bbootstrapApplication\(/;
|
|
87
|
+
/**
|
|
88
|
+
* Adds `provideStreamConfig({ ...STREAM_DEFAULT_COMPONENTS })` as the first provider of an application config,
|
|
89
|
+
* or returns `null` when the file is no application config with a literal `providers` array.
|
|
90
|
+
*/
|
|
91
|
+
export const addStreamConfigToAppConfig = (content) => {
|
|
92
|
+
if (!APP_CONFIG_MARKER.test(content) || content.includes('mergeApplicationConfig('))
|
|
93
|
+
return null;
|
|
94
|
+
const providers = content.match(/\bproviders\s*:\s*\[(\s*\])?/);
|
|
95
|
+
if (!providers || providers.index === undefined)
|
|
96
|
+
return null;
|
|
97
|
+
const provider = 'provideStreamConfig({ ...STREAM_DEFAULT_COMPONENTS })';
|
|
98
|
+
const insertAt = providers.index + providers[0].length;
|
|
99
|
+
const withProvider = providers[1]
|
|
100
|
+
? `${content.slice(0, providers.index)}providers: [${provider}]${content.slice(insertAt)}`
|
|
101
|
+
: `${content.slice(0, insertAt)}${provider}, ${content.slice(insertAt)}`;
|
|
102
|
+
const importStatement = [...withProvider.matchAll(COMPONENTS_IMPORT)][0];
|
|
103
|
+
if (!importStatement || importStatement.index === undefined) {
|
|
104
|
+
return `import { STREAM_DEFAULT_COMPONENTS, provideStreamConfig } from '@ethlete/components';\n${withProvider}`;
|
|
105
|
+
}
|
|
106
|
+
const rewrittenImport = importStatement[0].replace('{', '{ STREAM_DEFAULT_COMPONENTS, provideStreamConfig,');
|
|
107
|
+
return (withProvider.slice(0, importStatement.index) +
|
|
108
|
+
rewrittenImport +
|
|
109
|
+
withProvider.slice(importStatement.index + importStatement[0].length));
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=stream-default-components.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runRemovedExportsMigration, } from '../removed-exports/run-removed-exports-migration.js';
|
|
2
|
+
import { YOUTUBE_PLAYER_SLOT_DIRECTIVE_REMOVALS } from './youtube-player-slot-directive.js';
|
|
3
|
+
export const YOUTUBE_PLAYER_SLOT_DIRECTIVE_REPORT_PATH = 'youtube-player-slot-directive-migration-tasks.md';
|
|
4
|
+
export default async function migrateYoutubePlayerSlotDirective(tree, schema) {
|
|
5
|
+
await runRemovedExportsMigration(tree, schema, {
|
|
6
|
+
title: 'Removing YoutubePlayerSlotDirective and YOUTUBE_PLAYER_SLOT_TOKEN',
|
|
7
|
+
removed: YOUTUBE_PLAYER_SLOT_DIRECTIVE_REMOVALS,
|
|
8
|
+
reportPath: YOUTUBE_PLAYER_SLOT_DIRECTIVE_REPORT_PATH,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-youtube-player-slot-directive",
|
|
4
|
+
"title": "Remove YoutubePlayerSlotDirective and YOUTUBE_PLAYER_SLOT_TOKEN, and mark the uses it cannot remove",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to scan. Defaults to every project in the workspace.",
|
|
11
|
+
"alias": "project"
|
|
12
|
+
},
|
|
13
|
+
"include": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" },
|
|
16
|
+
"description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
19
|
+
"skipFormat": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Skip formatting files after migration",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const YOUTUBE_PLAYER_SLOT_DIRECTIVE_REMOVALS = [
|
|
2
|
+
{
|
|
3
|
+
name: 'YoutubePlayerSlotDirective',
|
|
4
|
+
dropFromArrays: ['imports'],
|
|
5
|
+
todo: 'YoutubePlayerSlotDirective is removed. Render <et-youtube-player-slot> (YoutubePlayerSlotComponent) or build a slot with createStreamPlayerSlot.',
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: 'YOUTUBE_PLAYER_SLOT_TOKEN',
|
|
9
|
+
todo: 'YOUTUBE_PLAYER_SLOT_TOKEN is removed. Query YoutubePlayerSlotComponent instead, or read the slot through STREAM_SLOT_PLAYER_ID_TOKEN.',
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
//# sourceMappingURL=youtube-player-slot-directive.js.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export const MIGRATION_TODO = 'TODO(ethlete-migration)';
|
|
2
|
+
const COMPONENTS_IMPORT = /import\s+(type\s+)?\{([^}]*)\}\s*from\s*['"]@ethlete\/components['"][ \t]*;?/g;
|
|
3
|
+
const escape = (value) => value.replace(/[$]/g, '\\$');
|
|
4
|
+
const useOf = (local) => new RegExp(`(?<![\\w$.])${escape(local)}(?![\\w$])`, 'g');
|
|
5
|
+
const parseSpecifiers = (list) => list
|
|
6
|
+
.split(',')
|
|
7
|
+
.map((raw) => raw.trim())
|
|
8
|
+
.filter((raw) => raw.length > 0)
|
|
9
|
+
.map((raw) => {
|
|
10
|
+
const match = /^(type\s+)?([\w$]+)(?:\s+as\s+([\w$]+))?$/.exec(raw);
|
|
11
|
+
return {
|
|
12
|
+
raw,
|
|
13
|
+
imported: match?.[2] ?? raw,
|
|
14
|
+
local: match?.[3] ?? match?.[2] ?? raw,
|
|
15
|
+
typeOnly: match?.[1] !== undefined,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
const printSpecifier = (specifier) => `${specifier.typeOnly ? 'type ' : ''}${specifier.imported}${specifier.local === specifier.imported ? '' : ` as ${specifier.local}`}`;
|
|
19
|
+
const closingBracket = (content, open) => {
|
|
20
|
+
let depth = 0;
|
|
21
|
+
for (let index = open; index < content.length; index++) {
|
|
22
|
+
const char = content[index];
|
|
23
|
+
if (char === '[' || char === '(' || char === '{')
|
|
24
|
+
depth++;
|
|
25
|
+
if (char === ']' || char === ')' || char === '}')
|
|
26
|
+
depth--;
|
|
27
|
+
if (depth === 0)
|
|
28
|
+
return index;
|
|
29
|
+
}
|
|
30
|
+
return -1;
|
|
31
|
+
};
|
|
32
|
+
const splitTopLevel = (list) => {
|
|
33
|
+
const parts = [];
|
|
34
|
+
let depth = 0;
|
|
35
|
+
let start = 0;
|
|
36
|
+
for (let index = 0; index < list.length; index++) {
|
|
37
|
+
const char = list[index];
|
|
38
|
+
if (char === '[' || char === '(' || char === '{')
|
|
39
|
+
depth++;
|
|
40
|
+
if (char === ']' || char === ')' || char === '}')
|
|
41
|
+
depth--;
|
|
42
|
+
if (char === ',' && depth === 0) {
|
|
43
|
+
parts.push(list.slice(start, index));
|
|
44
|
+
start = index + 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
parts.push(list.slice(start));
|
|
48
|
+
return parts;
|
|
49
|
+
};
|
|
50
|
+
const dropFromArray = (content, key, local) => {
|
|
51
|
+
const opening = new RegExp(`\\b${key}\\s*:\\s*\\[`, 'g');
|
|
52
|
+
let next = content;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = opening.exec(next)) !== null) {
|
|
55
|
+
const open = match.index + match[0].length - 1;
|
|
56
|
+
const close = closingBracket(next, open);
|
|
57
|
+
if (close === -1)
|
|
58
|
+
break;
|
|
59
|
+
const parts = splitTopLevel(next.slice(open + 1, close));
|
|
60
|
+
const kept = parts.filter((part) => part.trim() !== local);
|
|
61
|
+
if (kept.length === parts.length)
|
|
62
|
+
continue;
|
|
63
|
+
const elements = kept.map((part) => part.trim()).filter((part) => part.length > 0);
|
|
64
|
+
next = `${next.slice(0, open + 1)}${elements.join(', ')}${next.slice(close)}`;
|
|
65
|
+
opening.lastIndex = open + 1;
|
|
66
|
+
}
|
|
67
|
+
return next;
|
|
68
|
+
};
|
|
69
|
+
const markerFor = (todo) => `// ${MIGRATION_TODO}: ${todo}`;
|
|
70
|
+
const addMarkers = (content, local, todo) => {
|
|
71
|
+
const marker = markerFor(todo);
|
|
72
|
+
const output = [];
|
|
73
|
+
for (const line of content.split('\n')) {
|
|
74
|
+
const code = line.trimStart();
|
|
75
|
+
const isComment = code.startsWith('//') || code.startsWith('*') || code.startsWith('/*');
|
|
76
|
+
if (!isComment && useOf(local).test(line) && output.at(-1)?.trim() !== marker) {
|
|
77
|
+
output.push(`${line.slice(0, line.length - code.length)}${marker}`);
|
|
78
|
+
}
|
|
79
|
+
output.push(line);
|
|
80
|
+
}
|
|
81
|
+
return output.join('\n');
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Rewrites the named imports of removed `@ethlete/components` exports: renames the ones with a successor,
|
|
85
|
+
* drops a removed one from the arrays where it is safe to, and marks every use left with a
|
|
86
|
+
* `TODO(ethlete-migration)` comment. A marked name keeps its import, so the build points at it too.
|
|
87
|
+
*/
|
|
88
|
+
export const migrateRemovedExportsInFile = (filePath, content, removed) => {
|
|
89
|
+
const byName = new Map(removed.map((entry) => [entry.name, entry]));
|
|
90
|
+
const statements = [...content.matchAll(COMPONENTS_IMPORT)].filter((match) => parseSpecifiers(match[2] ?? '').some((specifier) => byName.has(specifier.imported)));
|
|
91
|
+
if (statements.length === 0)
|
|
92
|
+
return { next: null, tasks: [] };
|
|
93
|
+
const renames = [];
|
|
94
|
+
const dropped = [];
|
|
95
|
+
const placeholders = [];
|
|
96
|
+
let body = '';
|
|
97
|
+
let cursor = 0;
|
|
98
|
+
statements.forEach((statement, index) => {
|
|
99
|
+
const start = statement.index ?? 0;
|
|
100
|
+
body += content.slice(cursor, start) + `\uE000${index}\uE000`;
|
|
101
|
+
cursor = start + statement[0].length;
|
|
102
|
+
placeholders.push(statement[0]);
|
|
103
|
+
});
|
|
104
|
+
body += content.slice(cursor);
|
|
105
|
+
const specifiersOf = statements.map((statement) => parseSpecifiers(statement[2] ?? '').map((specifier) => {
|
|
106
|
+
const entry = byName.get(specifier.imported);
|
|
107
|
+
if (!entry)
|
|
108
|
+
return specifier;
|
|
109
|
+
if (entry.renameTo) {
|
|
110
|
+
if (specifier.local === specifier.imported)
|
|
111
|
+
renames.push([specifier.local, entry.renameTo]);
|
|
112
|
+
return {
|
|
113
|
+
...specifier,
|
|
114
|
+
imported: entry.renameTo,
|
|
115
|
+
local: specifier.local === specifier.imported ? entry.renameTo : specifier.local,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
dropped.push({ specifier, entry });
|
|
119
|
+
return specifier;
|
|
120
|
+
}));
|
|
121
|
+
for (const [from, to] of renames)
|
|
122
|
+
body = body.replace(useOf(from), to);
|
|
123
|
+
const removedLocals = new Set();
|
|
124
|
+
for (const { specifier, entry } of dropped) {
|
|
125
|
+
for (const key of entry.dropFromArrays ?? [])
|
|
126
|
+
body = dropFromArray(body, key, specifier.local);
|
|
127
|
+
if (useOf(specifier.local).test(body))
|
|
128
|
+
body = addMarkers(body, specifier.local, entry.todo);
|
|
129
|
+
else
|
|
130
|
+
removedLocals.add(specifier.local);
|
|
131
|
+
}
|
|
132
|
+
const next = body.replace(/\uE000(\d+)\uE000(\n?)/g, (_, index, newline) => {
|
|
133
|
+
const statement = statements[Number(index)];
|
|
134
|
+
const specifiers = (specifiersOf[Number(index)] ?? []).filter((specifier) => !removedLocals.has(specifier.local) || !byName.has(specifier.imported));
|
|
135
|
+
const original = placeholders[Number(index)] ?? '';
|
|
136
|
+
if (specifiers.length === 0)
|
|
137
|
+
return '';
|
|
138
|
+
const list = statement?.[2] ?? '';
|
|
139
|
+
const multiline = list.includes('\n');
|
|
140
|
+
const printed = multiline
|
|
141
|
+
? `{\n${specifiers.map((specifier) => ` ${printSpecifier(specifier)},`).join('\n')}\n}`
|
|
142
|
+
: `{ ${specifiers.map(printSpecifier).join(', ')} }`;
|
|
143
|
+
return original.replace(/\{[^}]*\}/, printed) + newline;
|
|
144
|
+
});
|
|
145
|
+
const markers = new Map(dropped.map(({ entry }) => [markerFor(entry.todo), entry]));
|
|
146
|
+
const tasks = [];
|
|
147
|
+
next.split('\n').forEach((line, index) => {
|
|
148
|
+
const entry = markers.get(line.trim());
|
|
149
|
+
if (entry)
|
|
150
|
+
tasks.push({ file: filePath, line: index + 2, name: entry.name, message: entry.todo });
|
|
151
|
+
});
|
|
152
|
+
return { next: next === content ? null : next, tasks };
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=removed-exports.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
|
|
3
|
+
import { MIGRATION_TODO, migrateRemovedExportsInFile } from './removed-exports.js';
|
|
4
|
+
const renderReport = (title, tasks) => [
|
|
5
|
+
`# ${title}`,
|
|
6
|
+
'',
|
|
7
|
+
`Each site below carries a \`// ${MIGRATION_TODO}\` comment the migration could not resolve. Fix it by hand, delete the comment, then delete this file.`,
|
|
8
|
+
'',
|
|
9
|
+
...tasks.map((task) => `- ${task.file}:${task.line} - \`${task.name}\`: ${task.message}`),
|
|
10
|
+
'',
|
|
11
|
+
].join('\n');
|
|
12
|
+
export const runRemovedExportsMigration = async (tree, schema, migration) => {
|
|
13
|
+
console.log(`\nš ${migration.title}...`);
|
|
14
|
+
const scope = createMigrationScope(tree, schema);
|
|
15
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
16
|
+
const changed = [];
|
|
17
|
+
const tasks = [];
|
|
18
|
+
scope.visit(tree, (filePath) => {
|
|
19
|
+
if (!/\.(ts|html)$/.test(filePath) || filePath.endsWith('.d.ts'))
|
|
20
|
+
return;
|
|
21
|
+
const content = tree.read(filePath, 'utf-8');
|
|
22
|
+
if (!content)
|
|
23
|
+
return;
|
|
24
|
+
const scan = filePath.endsWith('.ts')
|
|
25
|
+
? migrateRemovedExportsInFile(filePath, content, migration.removed)
|
|
26
|
+
: { next: null, tasks: [] };
|
|
27
|
+
const next = migration.rewrite?.(tree, filePath, scan.next ?? content) ?? scan.next;
|
|
28
|
+
tasks.push(...scan.tasks);
|
|
29
|
+
if (next !== null && next !== content) {
|
|
30
|
+
tree.write(filePath, next);
|
|
31
|
+
changed.push(filePath);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (tasks.length > 0)
|
|
35
|
+
tree.write(migration.reportPath, renderReport(migration.title, tasks));
|
|
36
|
+
if (!schema.skipFormat) {
|
|
37
|
+
await formatFiles(tree);
|
|
38
|
+
}
|
|
39
|
+
if (changed.length > 0)
|
|
40
|
+
console.log(`\nā
Rewrote ${changed.length} file(s).`);
|
|
41
|
+
if (tasks.length > 0) {
|
|
42
|
+
console.warn(`\nā ļø ${tasks.length} site(s) need a hand fix - search for ${MIGRATION_TODO}, or see ${migration.reportPath}.`);
|
|
43
|
+
}
|
|
44
|
+
else if (changed.length === 0) {
|
|
45
|
+
console.log('\nā
Nothing to do.');
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=run-removed-exports-migration.js.map
|
package/migrations.json
CHANGED
|
@@ -31,6 +31,70 @@
|
|
|
31
31
|
"description": "Find the rich text editors that lose the selection toolbar, and report where to opt back in.",
|
|
32
32
|
"generator": "@ethlete/components:migrate-rich-text-editor-floating-toolbar",
|
|
33
33
|
"docs": "/components/rich-text-editor"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "bracket-ethlete-renames",
|
|
37
|
+
"version": "1.0.0-next.64",
|
|
38
|
+
"kind": "auto",
|
|
39
|
+
"description": "Rename generateTournamentModeFormEthleteRounds to generateTournamentModeFromEthleteRounds.",
|
|
40
|
+
"generator": "@ethlete/components:migrate-bracket-ethlete-renames",
|
|
41
|
+
"docs": "/components/bracket"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "bracket-default-cards",
|
|
45
|
+
"version": "1.0.0-next.64",
|
|
46
|
+
"kind": "auto",
|
|
47
|
+
"description": "Spread BRACKET_DEFAULT_CARDS into provideBracketConfig, and report the brackets that need it by hand.",
|
|
48
|
+
"generator": "@ethlete/components:migrate-bracket-default-cards",
|
|
49
|
+
"docs": "/components/bracket"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "overlay-nav-tab-imports",
|
|
53
|
+
"version": "1.0.0-next.64",
|
|
54
|
+
"kind": "auto",
|
|
55
|
+
"description": "Add OVERLAY_NAV_TAB_IMPORTS where a NAV_TAB_IMPORTS component renders et-overlay-nav-tab-link.",
|
|
56
|
+
"generator": "@ethlete/components:migrate-overlay-nav-tab-imports",
|
|
57
|
+
"docs": "/components/tabs"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "youtube-player-slot-directive",
|
|
61
|
+
"version": "1.0.0-next.64",
|
|
62
|
+
"kind": "auto",
|
|
63
|
+
"description": "Remove YoutubePlayerSlotDirective and YOUTUBE_PLAYER_SLOT_TOKEN, and mark the uses that need a YoutubePlayerSlotComponent.",
|
|
64
|
+
"generator": "@ethlete/components:migrate-youtube-player-slot-directive",
|
|
65
|
+
"docs": "/components/stream"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "chart-baseline-y",
|
|
69
|
+
"version": "1.0.0-next.65",
|
|
70
|
+
"kind": "auto",
|
|
71
|
+
"description": "Rename the removed chart-grid baselineY alias to baseline.",
|
|
72
|
+
"generator": "@ethlete/components:migrate-chart-baseline-y",
|
|
73
|
+
"docs": "/components/chart"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "grid-engine-internals",
|
|
77
|
+
"version": "1.0.0-next.65",
|
|
78
|
+
"kind": "auto",
|
|
79
|
+
"description": "Remove the grid layout engine internals from @ethlete/components imports and mark the uses that need another approach.",
|
|
80
|
+
"generator": "@ethlete/components:migrate-grid-engine-internals",
|
|
81
|
+
"docs": "/components/grid"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "overlay-fullscreen-animation",
|
|
85
|
+
"version": "1.0.0-next.65",
|
|
86
|
+
"kind": "auto",
|
|
87
|
+
"description": "Remove the overlay fullscreen animation internals from @ethlete/components imports and mark the uses that need another approach.",
|
|
88
|
+
"generator": "@ethlete/components:migrate-overlay-fullscreen-animation",
|
|
89
|
+
"docs": "/components/overlays"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "stream-default-components",
|
|
93
|
+
"version": "1.0.0-next.65",
|
|
94
|
+
"kind": "auto",
|
|
95
|
+
"description": "Spread STREAM_DEFAULT_COMPONENTS into provideStreamConfig, add it to the app config of an app without one, and report the rest.",
|
|
96
|
+
"generator": "@ethlete/components:migrate-stream-default-components",
|
|
97
|
+
"docs": "/components/stream"
|
|
34
98
|
}
|
|
35
99
|
]
|
|
36
100
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/components",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.65",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@ethlete/bracket": "^1.0.0-
|
|
7
|
+
"@ethlete/bracket": "^1.0.0-next.2",
|
|
8
8
|
"tslib": "^2.3.0"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|