@ethlete/contentful 4.0.0-next.5 → 4.0.0-next.7

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.
@@ -0,0 +1,192 @@
1
+ import { formatFiles, updateJson } from '@nx/devkit';
2
+ import { createMigrationScope } from './migration-scope.js';
3
+ export const CONTENTFUL_V5_REPORT_PATH = 'contentful-v5-migration-tasks.md';
4
+ const COMPONENTS_PACKAGE = '@ethlete/components';
5
+ const COMPONENTS_VERSION = '^1.0.0-next.32';
6
+ /** Inputs that v5 dropped — the picture element now only carries static `et-picture-*` classes. */
7
+ const REMOVED_CLASS_INPUTS = ['imgClass', 'figureClass', 'figcaptionClass', 'pictureClass'];
8
+ /** Renderer internals that are no longer exported from `@ethlete/contentful`. */
9
+ const REMOVED_EXPORTS = [
10
+ 'RENDER_COMMAND_TYPE',
11
+ 'RENDER_COMMAND_POSITION',
12
+ 'HTML_OPEN_RENDER_COMMAND_POSITION',
13
+ 'HTML_CLOSE_RENDER_COMMAND_POSITION',
14
+ 'TEXT_RENDER_COMMAND_POSITION',
15
+ 'COMPONENT_RENDER_COMMAND_POSITION',
16
+ 'RENDER_INSTRUCTION_POSITION',
17
+ 'RENDER_INSTRUCTION_TYPE',
18
+ 'isHtmlOpenRenderCommand',
19
+ 'isHtmlCloseRenderCommand',
20
+ 'isTextRenderCommand',
21
+ 'isComponentRenderCommand',
22
+ 'getRenderCommandId',
23
+ 'isExecutedComponentCommandCacheItem',
24
+ 'isExecutedHtmlOrTextCommandCacheItem',
25
+ 'isExecutedTextCommandCacheItem',
26
+ ];
27
+ const IMAGE_TAG_REGEX = /<et-contentful-image\b[^>]*>/g;
28
+ const INLINE_TEMPLATE_REGEX = /template\s*:\s*`([\s\S]*?)`/g;
29
+ const CONTENTFUL_IMPORT_REGEX = /import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"]@ethlete\/contentful['"]/g;
30
+ const lineOf = (source, index) => source.slice(0, index).split('\n').length;
31
+ /** Renames `hasPriority` to `priority` and collects removed class inputs, on `et-contentful-image` only. */
32
+ const migrateTemplate = (template) => {
33
+ let changed = false;
34
+ const classInputHits = [];
35
+ const content = template.replace(IMAGE_TAG_REGEX, (tag, offset) => {
36
+ const foundInputs = REMOVED_CLASS_INPUTS.filter((input) => new RegExp(`(^|[\\s[])${input}(\\]|=|(?=[\\s/>]))`).test(tag));
37
+ if (foundInputs.length > 0) {
38
+ classInputHits.push({ line: lineOf(template, offset), inputs: foundInputs });
39
+ }
40
+ const next = tag
41
+ .replace(/\[hasPriority\]=/g, '[priority]=')
42
+ .replace(/(^|\s)hasPriority(=|(?=[\s/>]))/g, '$1priority$2');
43
+ if (next !== tag)
44
+ changed = true;
45
+ return next;
46
+ });
47
+ return { content, changed, classInputHits };
48
+ };
49
+ const migrateHtmlFile = (filePath, source) => {
50
+ const { content, changed, classInputHits } = migrateTemplate(source);
51
+ return { content, changed, tasks: classInputHits.map((hit) => createClassInputTask(filePath, hit)) };
52
+ };
53
+ const createClassInputTask = (filePath, hit) => ({
54
+ id: `image-class-input:${filePath}:${hit.line}`,
55
+ file: filePath,
56
+ line: hit.line,
57
+ message: `\`et-contentful-image\` still sets ${hit.inputs
58
+ .map((input) => `\`${input}\``)
59
+ .join(', ')}. Those inputs were removed — the rendered picture now only carries static \`et-picture-*\` classes. Drop the binding and target the \`et-picture-*\` classes from your own CSS instead.`,
60
+ });
61
+ /** Matches a whole `useTailwindClasses: <literal>,` property line — the option was removed in v5. */
62
+ const USE_TAILWIND_CLASSES_REGEX = /^[ \t]*useTailwindClasses\s*:\s*(?:true|false|[\w.]+)\s*,?[ \t]*\r?\n/gm;
63
+ const migrateTsFile = (filePath, source) => {
64
+ const tasks = [];
65
+ let changed = false;
66
+ source = source.replace(USE_TAILWIND_CLASSES_REGEX, () => {
67
+ changed = true;
68
+ return '';
69
+ });
70
+ const content = source.replace(INLINE_TEMPLATE_REGEX, (match, template, offset) => {
71
+ const result = migrateTemplate(template);
72
+ const templateStartLine = lineOf(source, offset + match.indexOf('`'));
73
+ for (const hit of result.classInputHits) {
74
+ tasks.push(createClassInputTask(filePath, { ...hit, line: templateStartLine + hit.line - 1 }));
75
+ }
76
+ if (!result.changed)
77
+ return match;
78
+ changed = true;
79
+ return match.replace(template, () => result.content);
80
+ });
81
+ for (const importMatch of source.matchAll(CONTENTFUL_IMPORT_REGEX)) {
82
+ const symbols = (importMatch[1] ?? '')
83
+ .split(',')
84
+ .map((specifier) => specifier
85
+ .trim()
86
+ .replace(/^type\s+/, '')
87
+ .split(/\s+as\s+/)[0]
88
+ ?.trim() ?? '')
89
+ .filter(Boolean);
90
+ for (const symbol of symbols) {
91
+ if (!REMOVED_EXPORTS.includes(symbol))
92
+ continue;
93
+ tasks.push({
94
+ id: `removed-export:${filePath}:${symbol}`,
95
+ file: filePath,
96
+ line: lineOf(source, importMatch.index ?? 0),
97
+ message: `\`${symbol}\` is no longer public API. The render pipeline moved to plain objects internally, so the renderer command helpers are gone. Remove the usage, or vendor the logic into your own code.`,
98
+ });
99
+ }
100
+ }
101
+ return { content, changed, tasks };
102
+ };
103
+ const migratePackageJson = (tree, filePath) => {
104
+ const tasks = [];
105
+ let changed = false;
106
+ updateJson(tree, filePath, (json) => {
107
+ const dependencies = json['dependencies'];
108
+ const peerDependencies = json['peerDependencies'];
109
+ const section = dependencies?.['@ethlete/contentful']
110
+ ? 'dependencies'
111
+ : peerDependencies?.['@ethlete/contentful']
112
+ ? 'peerDependencies'
113
+ : null;
114
+ if (!section)
115
+ return json;
116
+ const hasComponents = Boolean(dependencies?.[COMPONENTS_PACKAGE] ?? peerDependencies?.[COMPONENTS_PACKAGE]);
117
+ if (!hasComponents) {
118
+ json[section] = { ...json[section], [COMPONENTS_PACKAGE]: COMPONENTS_VERSION };
119
+ changed = true;
120
+ console.log(` ✓ ${filePath}: added ${COMPONENTS_PACKAGE}@${COMPONENTS_VERSION} to ${section}`);
121
+ }
122
+ if (dependencies?.['@ethlete/cdk'] ?? peerDependencies?.['@ethlete/cdk']) {
123
+ tasks.push({
124
+ id: `cdk-dependency:${filePath}`,
125
+ file: filePath,
126
+ message: '`@ethlete/contentful` no longer depends on `@ethlete/cdk`. The dependency was left in place because you may use it directly — if nothing in this project imports `@ethlete/cdk`, remove it.',
127
+ });
128
+ }
129
+ return json;
130
+ });
131
+ return { changed, tasks };
132
+ };
133
+ const renderReport = (tasks) => [
134
+ '# @ethlete/contentful v5 migration tasks',
135
+ '',
136
+ 'The codemod renamed the `et-contentful-image` `hasPriority` input to `priority`, removed the dropped',
137
+ '`useTailwindClasses` config option and made sure `@ethlete/components` is declared. The sites below',
138
+ 'need a decision a codemod cannot make.',
139
+ '',
140
+ 'Recipes:',
141
+ '',
142
+ '- Removed class inputs (`imgClass`, `figureClass`, `figcaptionClass`, `pictureClass`): the picture markup',
143
+ ' now carries static `et-picture-*` classes. Delete the binding and style those classes from your CSS.',
144
+ '- Removed renderer internals: the render pipeline is plain objects internally now. Drop the usage, or copy',
145
+ ' the helper into your own codebase.',
146
+ '- A leftover `@ethlete/cdk` dependency: remove it unless your own code imports it.',
147
+ '',
148
+ ...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}${task.line ? `:${task.line}` : ''}`, `- ${task.message}`, ''].join('\n')),
149
+ ].join('\n');
150
+ export default async function migrateToContentfulV5(tree, schema) {
151
+ console.log('\n🔄 Migrating to @ethlete/contentful v5...');
152
+ const scope = createMigrationScope(tree, schema);
153
+ console.log(` Scope: ${scope.describe()}`);
154
+ const tasks = [];
155
+ let filesChanged = 0;
156
+ scope.visit(tree, (filePath) => {
157
+ if (filePath === CONTENTFUL_V5_REPORT_PATH)
158
+ return;
159
+ if (filePath.endsWith('package.json')) {
160
+ const result = migratePackageJson(tree, filePath);
161
+ tasks.push(...result.tasks);
162
+ if (result.changed)
163
+ filesChanged++;
164
+ return;
165
+ }
166
+ const isHtml = filePath.endsWith('.html');
167
+ const isTs = filePath.endsWith('.ts') && !filePath.endsWith('.d.ts');
168
+ if (!isHtml && !isTs)
169
+ return;
170
+ const before = tree.read(filePath, 'utf-8');
171
+ if (!before)
172
+ return;
173
+ const result = isHtml ? migrateHtmlFile(filePath, before) : migrateTsFile(filePath, before);
174
+ tasks.push(...result.tasks);
175
+ if (result.changed) {
176
+ tree.write(filePath, result.content);
177
+ filesChanged++;
178
+ console.log(` ✓ ${filePath}`);
179
+ }
180
+ });
181
+ if (tasks.length > 0) {
182
+ tree.write(CONTENTFUL_V5_REPORT_PATH, renderReport(tasks));
183
+ }
184
+ if (!schema.skipFormat) {
185
+ await formatFiles(tree);
186
+ }
187
+ console.log(`\n✅ Updated ${filesChanged} file(s).`);
188
+ if (tasks.length > 0) {
189
+ console.log(`⚠️ ${tasks.length} site(s) need a manual decision — see ${CONTENTFUL_V5_REPORT_PATH}.`);
190
+ }
191
+ }
192
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-to-contentful-v5",
4
+ "title": "Migrate to @ethlete/contentful v5",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to migrate. Defaults to every project in the workspace. Every site is rewritten independently, so any scope is safe — use this to keep the diff reviewable.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to migrate, relative to the workspace root (e.g. 'libs/ui'). Unioned with `projects`. Defaults to the whole workspace.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
package/package.json CHANGED
@@ -1,22 +1,32 @@
1
1
  {
2
2
  "name": "@ethlete/contentful",
3
- "version": "4.0.0-next.5",
3
+ "version": "4.0.0-next.7",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@analogjs/vite-plugin-angular": "2.6.2",
8
- "@angular/cdk": "22.0.3",
9
- "@angular/common": "22.0.5",
10
- "@angular/core": "22.0.5",
11
- "@angular/router": "22.0.5",
7
+ "@analogjs/vite-plugin-angular": "2.6.3",
8
+ "@angular/common": "22.0.7",
9
+ "@angular/core": "22.0.7",
10
+ "@angular/router": "22.0.7",
12
11
  "@contentful/rich-text-types": "17.2.7",
13
- "@ethlete/cdk": "^5.0.0-beta.12",
12
+ "@ethlete/components": "^1.0.0-next.32",
14
13
  "@ethlete/core": "^5.0.0-beta.11",
15
14
  "@ethlete/query": "^6.0.0-beta.8",
16
- "@nx/vite": "23.1.0-beta.7",
17
- "rxjs": "7.8.2",
18
- "vite": "8.0.13"
15
+ "@nx/devkit": "23.1.0",
16
+ "vite": "8.1.5"
19
17
  },
18
+ "peerDependenciesMeta": {
19
+ "@analogjs/vite-plugin-angular": {
20
+ "optional": true
21
+ },
22
+ "@nx/devkit": {
23
+ "optional": true
24
+ },
25
+ "vite": {
26
+ "optional": true
27
+ }
28
+ },
29
+ "generators": "./generators/generators.json",
20
30
  "module": "fesm2022/ethlete-contentful.mjs",
21
31
  "typings": "types/ethlete-contentful.d.ts",
22
32
  "exports": {