@ethlete/components 1.0.0-next.62 ā 1.0.0-next.64
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 +345 -0
- package/fesm2022/ethlete-components.mjs +7749 -5026
- package/fesm2022/ethlete-components.mjs.map +1 -1
- package/generators/generators.json +20 -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-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-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 +49 -0
- package/migrations.json +32 -0
- package/package.json +2 -2
- package/types/ethlete-components.d.ts +3726 -3010
|
@@ -5,11 +5,26 @@
|
|
|
5
5
|
"schema": "./component/schema.json",
|
|
6
6
|
"description": "Scaffold a component domain following the three-tier architecture (folder layout, headless + default split, styles, spec, stories, docs page, barrel wiring)"
|
|
7
7
|
},
|
|
8
|
+
"migrate-bracket-default-cards": {
|
|
9
|
+
"factory": "./migrate-bracket-default-cards/migration",
|
|
10
|
+
"schema": "./migrate-bracket-default-cards/schema.json",
|
|
11
|
+
"description": "Spread BRACKET_DEFAULT_CARDS into provideBracketConfig and report brackets without one"
|
|
12
|
+
},
|
|
13
|
+
"migrate-bracket-ethlete-renames": {
|
|
14
|
+
"factory": "./migrate-bracket-ethlete-renames/migration",
|
|
15
|
+
"schema": "./migrate-bracket-ethlete-renames/schema.json",
|
|
16
|
+
"description": "Rename generateTournamentModeFormEthleteRounds to generateTournamentModeFromEthleteRounds"
|
|
17
|
+
},
|
|
8
18
|
"migrate-query-error-labels": {
|
|
9
19
|
"factory": "./migrate-query-error-labels/migration",
|
|
10
20
|
"schema": "./migrate-query-error-labels/schema.json",
|
|
11
21
|
"description": "Find query-error usage affected by the English-only label default and report where German must be re-opted-in"
|
|
12
22
|
},
|
|
23
|
+
"migrate-overlay-nav-tab-imports": {
|
|
24
|
+
"factory": "./migrate-overlay-nav-tab-imports/migration",
|
|
25
|
+
"schema": "./migrate-overlay-nav-tab-imports/schema.json",
|
|
26
|
+
"description": "Add OVERLAY_NAV_TAB_IMPORTS where a NAV_TAB_IMPORTS component renders et-overlay-nav-tab-link"
|
|
27
|
+
},
|
|
13
28
|
"migrate-rich-text-editor-floating-toolbar": {
|
|
14
29
|
"factory": "./migrate-rich-text-editor-floating-toolbar/migration",
|
|
15
30
|
"schema": "./migrate-rich-text-editor-floating-toolbar/schema.json",
|
|
@@ -25,6 +40,11 @@
|
|
|
25
40
|
"schema": "./migrate-rich-text-editor-tools/schema.json",
|
|
26
41
|
"description": "Find rich text editor usage that loses the heading, quote, code-block and link tools and report where to re-opt-in"
|
|
27
42
|
},
|
|
43
|
+
"migrate-youtube-player-slot-directive": {
|
|
44
|
+
"factory": "./migrate-youtube-player-slot-directive/migration",
|
|
45
|
+
"schema": "./migrate-youtube-player-slot-directive/schema.json",
|
|
46
|
+
"description": "Remove YoutubePlayerSlotDirective and YOUTUBE_PLAYER_SLOT_TOKEN, and mark the uses it cannot remove"
|
|
47
|
+
},
|
|
28
48
|
"icons": {
|
|
29
49
|
"factory": "./icons/generator",
|
|
30
50
|
"schema": "./icons/schema.json",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const USAGE_MARKERS = [
|
|
2
|
+
/<et-bracket[\s/>]/,
|
|
3
|
+
/<et-bracket-rounds-list[\s/>]/,
|
|
4
|
+
/\bBRACKET_IMPORTS\b/,
|
|
5
|
+
/\bBRACKET_ROUNDS_LIST_IMPORTS\b/,
|
|
6
|
+
/\bBracketComponent\b/,
|
|
7
|
+
/\bBracketRoundsListComponent\b/,
|
|
8
|
+
];
|
|
9
|
+
const COMPONENTS_IMPORT = /import\s*\{([^}]*)\}\s*from\s*['"]@ethlete\/components['"]/g;
|
|
10
|
+
const lineOf = (content, index) => content.slice(0, index).split('\n').length;
|
|
11
|
+
const taskId = (filePath, line) => `bracket-default-cards--${filePath.replace(/[^a-zA-Z0-9]+/g, '-')}-${line}`;
|
|
12
|
+
/**
|
|
13
|
+
* Spreads `BRACKET_DEFAULT_CARDS` first into every literal `provideBracketConfig` call, so a card the
|
|
14
|
+
* config names itself still wins, and reports each call whose config is not a literal.
|
|
15
|
+
*/
|
|
16
|
+
export const scanBracketDefaultCardsInFile = (filePath, content) => {
|
|
17
|
+
const usesBracket = USAGE_MARKERS.some((marker) => marker.test(content));
|
|
18
|
+
const hasConfig = content.includes('provideBracketConfig(');
|
|
19
|
+
const tasks = [];
|
|
20
|
+
const importStatement = [...content.matchAll(COMPONENTS_IMPORT)].find((match) => /\bprovideBracketConfig\b/.test(match[1] ?? ''));
|
|
21
|
+
if (!importStatement || content.includes('BRACKET_DEFAULT_CARDS')) {
|
|
22
|
+
return { next: null, usesBracket, hasConfig, tasks };
|
|
23
|
+
}
|
|
24
|
+
let rewrote = false;
|
|
25
|
+
const next = content.replace(/provideBracketConfig\(\s*(\{\s*\}|\{\s*|\)|[^\s{)])/g, (call, opening, index) => {
|
|
26
|
+
if (opening === ')') {
|
|
27
|
+
rewrote = true;
|
|
28
|
+
return 'provideBracketConfig({ ...BRACKET_DEFAULT_CARDS })';
|
|
29
|
+
}
|
|
30
|
+
if (opening.startsWith('{')) {
|
|
31
|
+
rewrote = true;
|
|
32
|
+
return opening.endsWith('}')
|
|
33
|
+
? 'provideBracketConfig({ ...BRACKET_DEFAULT_CARDS }'
|
|
34
|
+
: 'provideBracketConfig({ ...BRACKET_DEFAULT_CARDS, ';
|
|
35
|
+
}
|
|
36
|
+
const line = lineOf(content, index);
|
|
37
|
+
const argument = content.slice(index + call.length - 1).match(/^[^)\n]*/)?.[0] ?? '';
|
|
38
|
+
tasks.push({
|
|
39
|
+
id: taskId(filePath, line),
|
|
40
|
+
file: filePath,
|
|
41
|
+
line,
|
|
42
|
+
message: `\`provideBracketConfig(${argument})\` is not a literal the migration can edit. Spread \`BRACKET_DEFAULT_CARDS\` into it unless it names every card itself.`,
|
|
43
|
+
});
|
|
44
|
+
return call;
|
|
45
|
+
});
|
|
46
|
+
if (!rewrote)
|
|
47
|
+
return { next: null, usesBracket, hasConfig, tasks };
|
|
48
|
+
const rewrittenImport = importStatement[0].replace('{', '{ BRACKET_DEFAULT_CARDS,');
|
|
49
|
+
const importIndex = next.indexOf(importStatement[0]);
|
|
50
|
+
return {
|
|
51
|
+
next: next.slice(0, importIndex) + rewrittenImport + next.slice(importIndex + importStatement[0].length),
|
|
52
|
+
usesBracket,
|
|
53
|
+
hasConfig,
|
|
54
|
+
tasks,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export const bracketUsageTask = (filePath, content) => {
|
|
58
|
+
const index = Math.min(...USAGE_MARKERS.map((marker) => content.search(marker)).filter((position) => position >= 0));
|
|
59
|
+
const line = lineOf(content, index);
|
|
60
|
+
return {
|
|
61
|
+
id: taskId(filePath, line),
|
|
62
|
+
file: filePath,
|
|
63
|
+
line,
|
|
64
|
+
message: 'Renders a bracket, but the app has no `provideBracketConfig`, so no card is registered.',
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=bracket-default-cards.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
|
|
3
|
+
import { bracketUsageTask, scanBracketDefaultCardsInFile } from './bracket-default-cards.js';
|
|
4
|
+
export const BRACKET_DEFAULT_CARDS_REPORT_PATH = 'bracket-default-cards-migration-tasks.md';
|
|
5
|
+
const renderReport = (tasks) => [
|
|
6
|
+
'# Bracket default cards migration tasks',
|
|
7
|
+
'',
|
|
8
|
+
'The shipped bracket cards are opt-in. A bracket whose match or round header card nothing names throws',
|
|
9
|
+
'`ET3414`. The migration spread `BRACKET_DEFAULT_CARDS` into every literal `provideBracketConfig` call; the',
|
|
10
|
+
'sites below need it by hand. Add once, app-wide or on the component that renders the bracket:',
|
|
11
|
+
'',
|
|
12
|
+
'```ts',
|
|
13
|
+
"import { BRACKET_DEFAULT_CARDS, provideBracketConfig } from '@ethlete/components';",
|
|
14
|
+
'',
|
|
15
|
+
'provideBracketConfig({ ...BRACKET_DEFAULT_CARDS });',
|
|
16
|
+
'```',
|
|
17
|
+
'',
|
|
18
|
+
'A bracket that binds cards of its own needs nothing. Delete this file when done.',
|
|
19
|
+
'',
|
|
20
|
+
...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}:${task.line}`, `- ${task.message}`, ''].join('\n')),
|
|
21
|
+
].join('\n');
|
|
22
|
+
export default async function migrateBracketDefaultCards(tree, schema) {
|
|
23
|
+
console.log('\nš Spreading BRACKET_DEFAULT_CARDS into provideBracketConfig...');
|
|
24
|
+
const scope = createMigrationScope(tree, schema);
|
|
25
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
26
|
+
const changed = [];
|
|
27
|
+
const tasks = [];
|
|
28
|
+
const usages = [];
|
|
29
|
+
let hasConfig = false;
|
|
30
|
+
scope.visit(tree, (filePath) => {
|
|
31
|
+
if (!/\.(ts|html)$/.test(filePath) || filePath.endsWith('.d.ts'))
|
|
32
|
+
return;
|
|
33
|
+
const content = tree.read(filePath, 'utf-8');
|
|
34
|
+
if (!content)
|
|
35
|
+
return;
|
|
36
|
+
const result = scanBracketDefaultCardsInFile(filePath, content);
|
|
37
|
+
hasConfig ||= result.hasConfig;
|
|
38
|
+
tasks.push(...result.tasks);
|
|
39
|
+
if (result.usesBracket)
|
|
40
|
+
usages.push(bracketUsageTask(filePath, content));
|
|
41
|
+
if (result.next !== null) {
|
|
42
|
+
tree.write(filePath, result.next);
|
|
43
|
+
changed.push(filePath);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const report = hasConfig ? tasks : [...tasks, ...usages];
|
|
47
|
+
if (report.length > 0) {
|
|
48
|
+
tree.write(BRACKET_DEFAULT_CARDS_REPORT_PATH, renderReport(report));
|
|
49
|
+
}
|
|
50
|
+
if (!schema.skipFormat) {
|
|
51
|
+
await formatFiles(tree);
|
|
52
|
+
}
|
|
53
|
+
if (changed.length > 0) {
|
|
54
|
+
console.log(`\nā
Spread BRACKET_DEFAULT_CARDS in ${changed.length} file(s).`);
|
|
55
|
+
}
|
|
56
|
+
if (report.length > 0) {
|
|
57
|
+
console.log(`\nā ļø ${report.length} site(s) need the default cards by hand ā see ${BRACKET_DEFAULT_CARDS_REPORT_PATH}.`);
|
|
58
|
+
}
|
|
59
|
+
else if (changed.length === 0) {
|
|
60
|
+
console.log('\nā
Nothing to do.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-bracket-default-cards",
|
|
4
|
+
"title": "Spread BRACKET_DEFAULT_CARDS into provideBracketConfig and report brackets 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,15 @@
|
|
|
1
|
+
export const BRACKET_ETHLETE_RENAMES = {
|
|
2
|
+
generateTournamentModeFormEthleteRounds: 'generateTournamentModeFromEthleteRounds',
|
|
3
|
+
};
|
|
4
|
+
const IMPORTS_COMPONENTS = /from\s+['"]@ethlete\/components['"]/;
|
|
5
|
+
/** Returns the file with every renamed bracket export replaced, or `null` when nothing changed. */
|
|
6
|
+
export const renameBracketEthleteExportsInFile = (content) => {
|
|
7
|
+
if (!IMPORTS_COMPONENTS.test(content))
|
|
8
|
+
return null;
|
|
9
|
+
let next = content;
|
|
10
|
+
for (const [oldName, newName] of Object.entries(BRACKET_ETHLETE_RENAMES)) {
|
|
11
|
+
next = next.replace(new RegExp(`\\b${oldName}\\b`, 'g'), newName);
|
|
12
|
+
}
|
|
13
|
+
return next === content ? null : next;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=bracket-ethlete-renames.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
|
|
3
|
+
import { renameBracketEthleteExportsInFile } from './bracket-ethlete-renames.js';
|
|
4
|
+
export default async function migrateBracketEthleteRenames(tree, schema) {
|
|
5
|
+
console.log('\nš Renaming generateTournamentModeFormEthleteRounds to generateTournamentModeFromEthleteRounds...');
|
|
6
|
+
const scope = createMigrationScope(tree, schema);
|
|
7
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
8
|
+
const changed = [];
|
|
9
|
+
scope.visit(tree, (filePath) => {
|
|
10
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
|
|
11
|
+
return;
|
|
12
|
+
const content = tree.read(filePath, 'utf-8');
|
|
13
|
+
if (!content)
|
|
14
|
+
return;
|
|
15
|
+
const next = renameBracketEthleteExportsInFile(content);
|
|
16
|
+
if (next === null)
|
|
17
|
+
return;
|
|
18
|
+
tree.write(filePath, next);
|
|
19
|
+
changed.push(filePath);
|
|
20
|
+
});
|
|
21
|
+
if (!schema.skipFormat) {
|
|
22
|
+
await formatFiles(tree);
|
|
23
|
+
}
|
|
24
|
+
if (changed.length > 0) {
|
|
25
|
+
console.log(`\nā
Renamed the export in ${changed.length} file(s).`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log('\nā
Nothing to do.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-bracket-ethlete-renames",
|
|
4
|
+
"title": "Rename generateTournamentModeFormEthleteRounds to generateTournamentModeFromEthleteRounds",
|
|
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,35 @@
|
|
|
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 { addOverlayNavTabImportsToFile, TEMPLATE_URL } from './overlay-nav-tab-imports.js';
|
|
5
|
+
export default async function migrateOverlayNavTabImports(tree, schema) {
|
|
6
|
+
console.log('\nš Adding OVERLAY_NAV_TAB_IMPORTS where a NAV_TAB_IMPORTS component renders et-overlay-nav-tab-link...');
|
|
7
|
+
const scope = createMigrationScope(tree, schema);
|
|
8
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
9
|
+
const changed = [];
|
|
10
|
+
scope.visit(tree, (filePath) => {
|
|
11
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
|
|
12
|
+
return;
|
|
13
|
+
const content = tree.read(filePath, 'utf-8');
|
|
14
|
+
if (!content?.includes('NAV_TAB_IMPORTS'))
|
|
15
|
+
return;
|
|
16
|
+
const templates = [...content.matchAll(TEMPLATE_URL)]
|
|
17
|
+
.map((match) => tree.read(joinPathFragments(dirname(filePath), match[1] ?? ''), 'utf-8'))
|
|
18
|
+
.filter((template) => template !== null);
|
|
19
|
+
const next = addOverlayNavTabImportsToFile(content, templates);
|
|
20
|
+
if (next === null)
|
|
21
|
+
return;
|
|
22
|
+
tree.write(filePath, next);
|
|
23
|
+
changed.push(filePath);
|
|
24
|
+
});
|
|
25
|
+
if (!schema.skipFormat) {
|
|
26
|
+
await formatFiles(tree);
|
|
27
|
+
}
|
|
28
|
+
if (changed.length > 0) {
|
|
29
|
+
console.log(`\nā
Added OVERLAY_NAV_TAB_IMPORTS in ${changed.length} file(s).`);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log('\nā
Nothing to do.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const COMPONENTS_IMPORT = /import\s*\{([^}]*)\}\s*from\s*['"]@ethlete\/components['"]/g;
|
|
2
|
+
const NAV_TAB_IMPORTS = /\bNAV_TAB_IMPORTS\b/;
|
|
3
|
+
const OVERLAY_LINK = 'et-overlay-nav-tab-link';
|
|
4
|
+
export const TEMPLATE_URL = /templateUrl\s*:\s*['"]([^'"]+)['"]/g;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the file with `OVERLAY_NAV_TAB_IMPORTS` added next to every `NAV_TAB_IMPORTS`, or `null` when the
|
|
7
|
+
* file does not import `NAV_TAB_IMPORTS` from `@ethlete/components` or none of its templates renders an
|
|
8
|
+
* `et-overlay-nav-tab-link`.
|
|
9
|
+
*/
|
|
10
|
+
export const addOverlayNavTabImportsToFile = (content, externalTemplates) => {
|
|
11
|
+
if (content.includes('OVERLAY_NAV_TAB_IMPORTS'))
|
|
12
|
+
return null;
|
|
13
|
+
const importStatement = [...content.matchAll(COMPONENTS_IMPORT)].find((match) => NAV_TAB_IMPORTS.test(match[1] ?? '') && !/\bNAV_TAB_IMPORTS\s+as\b/.test(match[1] ?? ''));
|
|
14
|
+
if (!importStatement || importStatement.index === undefined)
|
|
15
|
+
return null;
|
|
16
|
+
if (!content.includes(OVERLAY_LINK) && !externalTemplates.some((template) => template.includes(OVERLAY_LINK))) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const start = importStatement.index;
|
|
20
|
+
const end = start + importStatement[0].length;
|
|
21
|
+
const rewrittenImport = importStatement[0].replace(NAV_TAB_IMPORTS, 'NAV_TAB_IMPORTS, OVERLAY_NAV_TAB_IMPORTS');
|
|
22
|
+
const addNextToUsage = (code) => code.replace(/(\.\.\.)?\bNAV_TAB_IMPORTS\b/g, (_, spread) => `${spread ?? ''}NAV_TAB_IMPORTS, ${spread ?? ''}OVERLAY_NAV_TAB_IMPORTS`);
|
|
23
|
+
return addNextToUsage(content.slice(0, start)) + rewrittenImport + addNextToUsage(content.slice(end));
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=overlay-nav-tab-imports.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-overlay-nav-tab-imports",
|
|
4
|
+
"title": "Add OVERLAY_NAV_TAB_IMPORTS where a NAV_TAB_IMPORTS component renders et-overlay-nav-tab-link",
|
|
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,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
|
+
return 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,49 @@
|
|
|
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
|
+
return { changed, tasks };
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=run-removed-exports-migration.js.map
|
package/migrations.json
CHANGED
|
@@ -31,6 +31,38 @@
|
|
|
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"
|
|
34
66
|
}
|
|
35
67
|
]
|
|
36
68
|
}
|