@panneau/codemods 0.0.0-stage → 5.0.0-alpha.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.
- package/README.md +28 -2
- package/bin/panneau-codemods.js +5 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/context.d.ts +80 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +117 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/mappings.d.ts +49 -0
- package/dist/mappings.d.ts.map +1 -0
- package/dist/mappings.js +574 -0
- package/dist/mappings.js.map +1 -0
- package/dist/names.d.ts +5 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +13 -0
- package/dist/names.js.map +1 -0
- package/dist/run.d.ts +23 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +65 -0
- package/dist/run.js.map +1 -0
- package/dist/transform.d.ts +28 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +192 -0
- package/dist/transform.js.map +1 -0
- package/dist/transforms/bootstrap.d.ts +6 -0
- package/dist/transforms/bootstrap.d.ts.map +1 -0
- package/dist/transforms/bootstrap.js +65 -0
- package/dist/transforms/bootstrap.js.map +1 -0
- package/dist/transforms/entry.d.ts +15 -0
- package/dist/transforms/entry.d.ts.map +1 -0
- package/dist/transforms/entry.js +239 -0
- package/dist/transforms/entry.js.map +1 -0
- package/dist/transforms/fieldEntries.d.ts +20 -0
- package/dist/transforms/fieldEntries.d.ts.map +1 -0
- package/dist/transforms/fieldEntries.js +238 -0
- package/dist/transforms/fieldEntries.js.map +1 -0
- package/dist/transforms/fields.d.ts +3 -0
- package/dist/transforms/fields.d.ts.map +1 -0
- package/dist/transforms/fields.js +241 -0
- package/dist/transforms/fields.js.map +1 -0
- package/dist/transforms/imports.d.ts +3 -0
- package/dist/transforms/imports.d.ts.map +1 -0
- package/dist/transforms/imports.js +60 -0
- package/dist/transforms/imports.js.map +1 -0
- package/package.json +44 -5
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { addNamedImport, findImports, jsxString, removeSpecifier, removeUnusedImports } from "../context.js";
|
|
2
|
+
import { kebabCase } from "../names.js";
|
|
3
|
+
import { FIELDS_WITHOUT_TARGET, FIELD_PROPS, FIELD_RENAMES, getFieldTarget } from "../mappings.js";
|
|
4
|
+
//#region src/transforms/fields.ts
|
|
5
|
+
var CONTEXTS = "@panneau/core/contexts";
|
|
6
|
+
/** The local name of an export of a module, when the file imports it. */
|
|
7
|
+
function getImportedName(api, source, name) {
|
|
8
|
+
const { j } = api;
|
|
9
|
+
const specifiers = findImports(api, source).find(j.ImportSpecifier, { imported: { name } });
|
|
10
|
+
return specifiers.size() > 0 ? specifiers.get().node.local?.name ?? name : null;
|
|
11
|
+
}
|
|
12
|
+
function getAttributeName(attribute) {
|
|
13
|
+
if (attribute.type !== "JSXAttribute") return null;
|
|
14
|
+
return attribute.name.type === "JSXIdentifier" ? attribute.name.name : null;
|
|
15
|
+
}
|
|
16
|
+
function getElementName(element) {
|
|
17
|
+
const { name } = element.openingElement;
|
|
18
|
+
return name.type === "JSXIdentifier" ? name.name : null;
|
|
19
|
+
}
|
|
20
|
+
/** `useFieldComponent('text')` calls, as v5 components bound to the same names. */
|
|
21
|
+
function collectHookBindings(api, bindings) {
|
|
22
|
+
const { j, root } = api;
|
|
23
|
+
const hook = getImportedName(api, CONTEXTS, "useFieldComponent");
|
|
24
|
+
if (hook === null) return;
|
|
25
|
+
root.find(j.CallExpression, { callee: {
|
|
26
|
+
type: "Identifier",
|
|
27
|
+
name: hook
|
|
28
|
+
} }).forEach((path) => {
|
|
29
|
+
const [argument] = path.node.arguments;
|
|
30
|
+
const declarator = path.parent.node;
|
|
31
|
+
const id = argument !== void 0 && (argument.type === "StringLiteral" || argument.type === "Literal") && typeof argument.value === "string" ? argument.value : null;
|
|
32
|
+
if (id === null) {
|
|
33
|
+
api.report(path, "field-component", "`useFieldComponent()` with an id that is not a string: import the v5 component of the field", { todo: true });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const target = getFieldTarget(id);
|
|
37
|
+
if (target === null || declarator.type !== "VariableDeclarator" || declarator.id.type !== "Identifier") {
|
|
38
|
+
const replacement = FIELDS_WITHOUT_TARGET[kebabCase(id)];
|
|
39
|
+
api.report(path, "field-component", replacement !== void 0 ? `\`useFieldComponent('${id}')\`: ${replacement}` : target === null ? `\`useFieldComponent('${id}')\` renders a field of the project: import its component` : `\`useFieldComponent('${id}')\`: import \`${target.name}\` of \`${target.source}\``, { todo: true });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
bindings.set(declarator.id.name, target);
|
|
43
|
+
const declaration = path.parent.parent;
|
|
44
|
+
if (declaration.node.declarations.length === 1) j(declaration).remove();
|
|
45
|
+
else j(path.parent).remove();
|
|
46
|
+
});
|
|
47
|
+
if (root.find(j.CallExpression, { callee: {
|
|
48
|
+
type: "Identifier",
|
|
49
|
+
name: hook
|
|
50
|
+
} }).size() === 0) removeUnusedImports(api, [hook]);
|
|
51
|
+
}
|
|
52
|
+
/** Default and named imports of `@panneau/field-*` packages, as v5 components. */
|
|
53
|
+
function collectImportBindings(api, bindings) {
|
|
54
|
+
const { j, root } = api;
|
|
55
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
56
|
+
const source = String(path.node.source.value);
|
|
57
|
+
const match = /^@panneau\/field-([a-z0-9-]+)$/.exec(source);
|
|
58
|
+
const target = match?.[1] !== void 0 ? getFieldTarget(match[1]) : null;
|
|
59
|
+
if (target === null) return;
|
|
60
|
+
j(path).find(j.Specifier).forEach((specifier) => {
|
|
61
|
+
const local = specifier.node.local?.name;
|
|
62
|
+
if (local === void 0 || specifier.node.type === "ImportNamespaceSpecifier") return;
|
|
63
|
+
bindings.set(local, target);
|
|
64
|
+
removeSpecifier(api, specifier);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** The value of a `label` that is a message descriptor of `defineMessages()`. */
|
|
69
|
+
function isMessageDescriptor(api, attribute) {
|
|
70
|
+
if (attribute.type !== "JSXAttribute" || attribute.value?.type !== "JSXExpressionContainer") return false;
|
|
71
|
+
const expression = attribute.value.expression;
|
|
72
|
+
if (expression.type !== "MemberExpression" || expression.object.type !== "Identifier") return false;
|
|
73
|
+
return api.root.findVariableDeclarators(expression.object.name).filter((path) => {
|
|
74
|
+
const init = path.node.init;
|
|
75
|
+
return init?.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "defineMessages";
|
|
76
|
+
}).size() > 0;
|
|
77
|
+
}
|
|
78
|
+
/** `fieldComponent={TextField}` of a v4 localized field, as the render function of v5. */
|
|
79
|
+
function convertFieldComponent(api, path, bindings) {
|
|
80
|
+
const { j } = api;
|
|
81
|
+
const element = path.node;
|
|
82
|
+
const name = getElementName(element) ?? "";
|
|
83
|
+
const attributes = element.openingElement.attributes ?? [];
|
|
84
|
+
const attribute = attributes.find((candidate) => getAttributeName(candidate) === "fieldComponent");
|
|
85
|
+
if (attribute === void 0 || attribute.type !== "JSXAttribute") {
|
|
86
|
+
if (!(element.children ?? []).some((child) => !(child.type === "JSXText" && child.value.trim() === ""))) api.report(path, "field-component", `\`<${name}>\` has no \`fieldComponent\`: v4 rendered the \`component\` of the field${attributes.some((candidate) => candidate.type === "JSXSpreadAttribute") ? " (given by the spread props)" : ""}, \`LocalizedField\` of Panneau 5 renders the input of each locale with its children function: give it one`, { todo: true });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const value = attribute.value;
|
|
90
|
+
if (value?.type !== "JSXExpressionContainer" || value.expression.type !== "Identifier") {
|
|
91
|
+
api.report(path, "field-component", "`fieldComponent` of a localized field: render the input of each locale with the children function of `LocalizedField`", { todo: true });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const component = value.expression.name;
|
|
95
|
+
const target = bindings.get(component);
|
|
96
|
+
const input = j.identifier("input");
|
|
97
|
+
const member = (prop) => j.memberExpression(input, j.identifier(prop));
|
|
98
|
+
const prop = (prop, from = prop) => {
|
|
99
|
+
const expression = prop === "value" && api.typescript && target?.valueType !== void 0 ? j.tsAsExpression(member(from), j.tsUnionType([target.valueType === "string" ? j.tsStringKeyword() : target.valueType === "number" ? j.tsNumberKeyword() : j.tsBooleanKeyword(), j.tsNullKeyword()])) : member(from);
|
|
100
|
+
return j.jsxAttribute(j.jsxIdentifier(prop), j.jsxExpressionContainer(expression));
|
|
101
|
+
};
|
|
102
|
+
const field = j.jsxElement(j.jsxOpeningElement(j.jsxIdentifier(component), [...[
|
|
103
|
+
"label",
|
|
104
|
+
"value",
|
|
105
|
+
"onChange",
|
|
106
|
+
"onBlur",
|
|
107
|
+
"disabled",
|
|
108
|
+
"readOnly",
|
|
109
|
+
"required"
|
|
110
|
+
].map((name) => prop(name)), ...target === void 0 ? [prop("fieldLocale", "locale")] : []], true));
|
|
111
|
+
element.openingElement.attributes = attributes.filter((candidate) => candidate !== attribute);
|
|
112
|
+
element.openingElement.selfClosing = false;
|
|
113
|
+
element.closingElement = j.jsxClosingElement(j.jsxIdentifier(name));
|
|
114
|
+
element.children = [j.jsxExpressionContainer(j.arrowFunctionExpression([input], field))];
|
|
115
|
+
}
|
|
116
|
+
/** Renames, adds and reports the props of the elements of v5 components. */
|
|
117
|
+
function convertElements(api, bindings) {
|
|
118
|
+
const { j, root } = api;
|
|
119
|
+
const noted = /* @__PURE__ */ new Set();
|
|
120
|
+
root.find(j.JSXElement).forEach((path) => {
|
|
121
|
+
const name = getElementName(path.node);
|
|
122
|
+
const target = name !== null ? bindings.get(name) : void 0;
|
|
123
|
+
if (name === null || target === void 0) return;
|
|
124
|
+
if (target.name === "LocalizedField") convertFieldComponent(api, path, bindings);
|
|
125
|
+
const opening = path.node.openingElement;
|
|
126
|
+
const renames = {
|
|
127
|
+
...FIELD_RENAMES,
|
|
128
|
+
...target.renames
|
|
129
|
+
};
|
|
130
|
+
const known = /* @__PURE__ */ new Set([...FIELD_PROPS, ...target.props]);
|
|
131
|
+
const unknown = [];
|
|
132
|
+
(opening.attributes ?? []).forEach((attribute) => {
|
|
133
|
+
const attributeName = getAttributeName(attribute);
|
|
134
|
+
if (attributeName === null || attribute.type !== "JSXAttribute") return;
|
|
135
|
+
const renamed = renames[attributeName];
|
|
136
|
+
if (renamed !== void 0) attribute.name = j.jsxIdentifier(renamed);
|
|
137
|
+
else if (!known.has(attributeName)) unknown.push(attributeName);
|
|
138
|
+
if (attributeName === "label" && isMessageDescriptor(api, attribute)) api.report(path, "field-component", `\`<${name} label>\` is a message descriptor: labels of Panneau 5 are nodes, format it (\`intl.formatMessage()\`)`, { todo: true });
|
|
139
|
+
});
|
|
140
|
+
Object.entries(target.adds ?? {}).forEach(([prop, value]) => {
|
|
141
|
+
if ((opening.attributes ?? []).some((attribute) => getAttributeName(attribute) === prop)) return;
|
|
142
|
+
const attributeValue = value === true ? null : typeof value === "string" ? jsxString(j, value) : j.jsxExpressionContainer(j.arrayExpression(value.map((item) => j.stringLiteral(item))));
|
|
143
|
+
opening.attributes = [...opening.attributes ?? [], j.jsxAttribute(j.jsxIdentifier(prop), attributeValue)];
|
|
144
|
+
});
|
|
145
|
+
if (target.name === "LocalizedField" && !(opening.attributes ?? []).some((a) => getAttributeName(a) === "locales")) api.report(path, "field-component", "`<LocalizedField>` needs `locales`: `useFieldsContext().locales` of `@panneau/fields` gives those of the form", { todo: true });
|
|
146
|
+
if (unknown.length > 0) api.report(path, "field-component", `\`<${name}>\`: ${unknown.map((prop) => `\`${prop}\``).join(", ")} ${unknown.length > 1 ? "are not props" : "is not a prop"} of \`${target.name}\` of \`${target.source}\``, { todo: true });
|
|
147
|
+
if (target.note !== void 0 && !noted.has(name)) {
|
|
148
|
+
noted.add(name);
|
|
149
|
+
api.report(path, "field-component", `\`<${name}>\` is \`${target.name}\` of \`${target.source}\`: ${target.note}`, { todo: true });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `<FormGroup label>` around one v5 field becomes the field's `label`: v5 fields render their own
|
|
155
|
+
* label, help and errors. Around anything else, it becomes a `Fieldset` of `@panneau/ui`.
|
|
156
|
+
*/
|
|
157
|
+
function convertFormGroups(api, bindings) {
|
|
158
|
+
const { j, root } = api;
|
|
159
|
+
findImports(api, "@panneau/element-form-group").find(j.ImportDefaultSpecifier).forEach((specifier) => {
|
|
160
|
+
const local = specifier.node.local?.name;
|
|
161
|
+
if (typeof local !== "string") return;
|
|
162
|
+
const formGroup = local;
|
|
163
|
+
let fieldset = null;
|
|
164
|
+
root.find(j.JSXElement, { openingElement: { name: { name: formGroup } } }).forEach((path) => {
|
|
165
|
+
const attributes = path.node.openingElement.attributes ?? [];
|
|
166
|
+
const children = (path.node.children ?? []).filter((child) => !(child.type === "JSXText" && child.value.trim() === ""));
|
|
167
|
+
const [child] = children;
|
|
168
|
+
const childName = child?.type === "JSXElement" ? getElementName(child) : null;
|
|
169
|
+
const others = attributes.filter((attribute) => {
|
|
170
|
+
const name = getAttributeName(attribute);
|
|
171
|
+
return name === null || ![
|
|
172
|
+
"label",
|
|
173
|
+
"helpText",
|
|
174
|
+
"className",
|
|
175
|
+
"key"
|
|
176
|
+
].includes(name);
|
|
177
|
+
});
|
|
178
|
+
if (others.length > 0) api.report(path, "form-group", `\`<${formGroup}>\`: ${others.map((attribute) => `\`${getAttributeName(attribute) ?? "..."}\``).join(", ")} ${others.length > 1 ? "have" : "has"} no equivalent in Panneau 5`, { todo: true });
|
|
179
|
+
const kept = attributes.filter((attribute) => !others.includes(attribute));
|
|
180
|
+
if (children.length === 1 && child?.type === "JSXElement" && childName !== null && bindings.has(childName)) {
|
|
181
|
+
const childAttributes = child.openingElement.attributes ?? [];
|
|
182
|
+
const has = (name) => childAttributes.some((attribute) => getAttributeName(attribute) === name);
|
|
183
|
+
const moved = kept.flatMap((attribute) => {
|
|
184
|
+
const name = getAttributeName(attribute);
|
|
185
|
+
const target = name === "helpText" ? "description" : name;
|
|
186
|
+
if (attribute.type !== "JSXAttribute" || target === null || has(target)) {
|
|
187
|
+
if (name === "className" && attribute.type === "JSXAttribute") api.report(path, "form-group", `\`<${formGroup} className>\` is dropped: \`<${childName}>\` has its own \`className\``, { todo: true });
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
return [j.jsxAttribute(j.jsxIdentifier(target), attribute.value)];
|
|
191
|
+
});
|
|
192
|
+
child.openingElement.attributes = [...moved, ...childAttributes];
|
|
193
|
+
j(path).replaceWith(child);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
fieldset ??= addNamedImport(api, "@panneau/ui", "Fieldset");
|
|
197
|
+
path.node.openingElement.name = j.jsxIdentifier(fieldset);
|
|
198
|
+
if (path.node.closingElement) path.node.closingElement.name = j.jsxIdentifier(fieldset);
|
|
199
|
+
path.node.openingElement.attributes = attributes.map((attribute) => {
|
|
200
|
+
const name = getAttributeName(attribute);
|
|
201
|
+
if (attribute.type === "JSXAttribute" && (name === "label" || name === "helpText")) attribute.name = j.jsxIdentifier(name === "label" ? "legend" : "description");
|
|
202
|
+
return attribute;
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
removeUnusedImports(api, [formGroup]);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/** `useFormValue()` reads the values of the form of `@panneau/forms`. */
|
|
209
|
+
function convertFormValue(api) {
|
|
210
|
+
const { j, root } = api;
|
|
211
|
+
const hook = getImportedName(api, CONTEXTS, "useFormValue");
|
|
212
|
+
if (hook === null) return;
|
|
213
|
+
root.find(j.CallExpression, { callee: {
|
|
214
|
+
type: "Identifier",
|
|
215
|
+
name: hook
|
|
216
|
+
} }).forEach((path) => {
|
|
217
|
+
if (path.node.arguments.length > 0) {
|
|
218
|
+
api.report(path, "form-value", "`useFormValue(path)`: `useFormContext().getValue(path)` of `@panneau/forms` reads a value of the form", { todo: true });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const useFormState = addNamedImport(api, "@panneau/forms", "useFormState");
|
|
222
|
+
const useFormContext = addNamedImport(api, "@panneau/forms", "useFormContext");
|
|
223
|
+
j(path).replaceWith(j.memberExpression(j.callExpression(j.identifier(useFormState), [j.callExpression(j.identifier(useFormContext), [])]), j.identifier("values")));
|
|
224
|
+
});
|
|
225
|
+
removeUnusedImports(api, [hook]);
|
|
226
|
+
}
|
|
227
|
+
function transformFields(api) {
|
|
228
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
229
|
+
collectHookBindings(api, bindings);
|
|
230
|
+
collectImportBindings(api, bindings);
|
|
231
|
+
bindings.forEach((target, local) => {
|
|
232
|
+
addNamedImport(api, target.source, target.name, { local });
|
|
233
|
+
});
|
|
234
|
+
convertElements(api, bindings);
|
|
235
|
+
convertFormGroups(api, bindings);
|
|
236
|
+
convertFormValue(api);
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
export { transformFields };
|
|
240
|
+
|
|
241
|
+
//# sourceMappingURL=fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.js","names":[],"sources":["../../src/transforms/fields.ts"],"sourcesContent":["// Fields that custom components render: `useFieldComponent('text')` and `@panneau/field-*`\n// become the v5 components, with their props; `FormGroup` becomes the field's label or a\n// `Fieldset`; `useFormValue()` reads the form of `@panneau/forms`.\nimport type {\n ASTPath,\n CallExpression,\n JSXAttribute,\n JSXElement,\n JSXExpressionContainer,\n JSXSpreadAttribute,\n VariableDeclarator,\n} from 'jscodeshift';\n\nimport {\n type TransformApi,\n addNamedImport,\n findImports,\n jsxString,\n removeSpecifier,\n removeUnusedImports,\n} from '../context';\nimport {\n FIELDS_WITHOUT_TARGET,\n FIELD_PROPS,\n FIELD_RENAMES,\n type FieldTarget,\n getFieldTarget,\n} from '../mappings';\nimport { kebabCase } from '../names';\n\nconst CONTEXTS = '@panneau/core/contexts';\n\ntype Attribute = JSXAttribute | JSXSpreadAttribute;\n\n/** The local name of an export of a module, when the file imports it. */\nfunction getImportedName(api: TransformApi, source: string, name: string): string | null {\n const { j } = api;\n const specifiers = findImports(api, source).find(j.ImportSpecifier, { imported: { name } });\n return specifiers.size() > 0 ? (specifiers.get().node.local?.name ?? name) : null;\n}\n\nfunction getAttributeName(attribute: Attribute): string | null {\n if (attribute.type !== 'JSXAttribute') {\n return null;\n }\n return attribute.name.type === 'JSXIdentifier' ? attribute.name.name : null;\n}\n\nfunction getElementName(element: JSXElement): string | null {\n const { name } = element.openingElement;\n return name.type === 'JSXIdentifier' ? name.name : null;\n}\n\n/** `useFieldComponent('text')` calls, as v5 components bound to the same names. */\nfunction collectHookBindings(api: TransformApi, bindings: Map<string, FieldTarget>): void {\n const { j, root } = api;\n const hook = getImportedName(api, CONTEXTS, 'useFieldComponent');\n if (hook === null) {\n return;\n }\n root.find(j.CallExpression, { callee: { type: 'Identifier', name: hook } }).forEach((path) => {\n const [argument] = path.node.arguments;\n const declarator = path.parent.node as VariableDeclarator;\n const id =\n argument !== undefined &&\n (argument.type === 'StringLiteral' || argument.type === 'Literal') &&\n typeof argument.value === 'string'\n ? argument.value\n : null;\n if (id === null) {\n api.report(\n path,\n 'field-component',\n '`useFieldComponent()` with an id that is not a string: import the v5 component of the field',\n { todo: true },\n );\n return;\n }\n const target = getFieldTarget(id);\n if (\n target === null ||\n declarator.type !== 'VariableDeclarator' ||\n declarator.id.type !== 'Identifier'\n ) {\n const replacement = FIELDS_WITHOUT_TARGET[kebabCase(id)];\n api.report(\n path,\n 'field-component',\n replacement !== undefined\n ? `\\`useFieldComponent('${id}')\\`: ${replacement}`\n : target === null\n ? `\\`useFieldComponent('${id}')\\` renders a field of the project: import its component`\n : `\\`useFieldComponent('${id}')\\`: import \\`${target.name}\\` of \\`${target.source}\\``,\n { todo: true },\n );\n return;\n }\n bindings.set(declarator.id.name, target);\n const declaration = path.parent.parent as ASTPath;\n const declarations = (declaration.node as { declarations: unknown[] }).declarations;\n if (declarations.length === 1) {\n j(declaration).remove();\n } else {\n j(path.parent as ASTPath).remove();\n }\n });\n if (root.find(j.CallExpression, { callee: { type: 'Identifier', name: hook } }).size() === 0) {\n removeUnusedImports(api, [hook]);\n }\n}\n\n/** Default and named imports of `@panneau/field-*` packages, as v5 components. */\nfunction collectImportBindings(api: TransformApi, bindings: Map<string, FieldTarget>): void {\n const { j, root } = api;\n root.find(j.ImportDeclaration).forEach((path) => {\n const source = String(path.node.source.value);\n const match = /^@panneau\\/field-([a-z0-9-]+)$/.exec(source);\n const target = match?.[1] !== undefined ? getFieldTarget(match[1]) : null;\n if (target === null) {\n return;\n }\n j(path)\n .find(j.Specifier)\n .forEach((specifier) => {\n const local = (specifier.node as { local?: { name?: string } | null }).local?.name;\n if (local === undefined || specifier.node.type === 'ImportNamespaceSpecifier') {\n return;\n }\n bindings.set(local, target);\n removeSpecifier(api, specifier as ASTPath);\n });\n });\n}\n\n/** The value of a `label` that is a message descriptor of `defineMessages()`. */\nfunction isMessageDescriptor(api: TransformApi, attribute: Attribute): boolean {\n if (attribute.type !== 'JSXAttribute' || attribute.value?.type !== 'JSXExpressionContainer') {\n return false;\n }\n const expression = attribute.value.expression;\n if (expression.type !== 'MemberExpression' || expression.object.type !== 'Identifier') {\n return false;\n }\n return (\n api.root\n .findVariableDeclarators(expression.object.name)\n .filter((path) => {\n const init = path.node.init;\n return (\n init?.type === 'CallExpression' &&\n init.callee.type === 'Identifier' &&\n init.callee.name === 'defineMessages'\n );\n })\n .size() > 0\n );\n}\n\n/** `fieldComponent={TextField}` of a v4 localized field, as the render function of v5. */\nfunction convertFieldComponent(\n api: TransformApi,\n path: ASTPath<JSXElement>,\n bindings: ReadonlyMap<string, FieldTarget>,\n): void {\n const { j } = api;\n const element = path.node;\n const name = getElementName(element) ?? '';\n const attributes = element.openingElement.attributes ?? [];\n const attribute = attributes.find(\n (candidate) => getAttributeName(candidate) === 'fieldComponent',\n );\n if (attribute === undefined || attribute.type !== 'JSXAttribute') {\n const hasChildren = (element.children ?? []).some(\n (child) => !(child.type === 'JSXText' && child.value.trim() === ''),\n );\n if (!hasChildren) {\n // v4 rendered the `component` of the field's definition, often given by `{...props}`;\n // the `LocalizedField` of v5 calls its children function for each locale.\n api.report(\n path,\n 'field-component',\n `\\`<${name}>\\` has no \\`fieldComponent\\`: v4 rendered the \\`component\\` of the field${attributes.some((candidate) => candidate.type === 'JSXSpreadAttribute') ? ' (given by the spread props)' : ''}, \\`LocalizedField\\` of Panneau 5 renders the input of each locale with its children function: give it one`,\n { todo: true },\n );\n }\n return;\n }\n const value = attribute.value as JSXExpressionContainer | null;\n if (value?.type !== 'JSXExpressionContainer' || value.expression.type !== 'Identifier') {\n api.report(\n path,\n 'field-component',\n '`fieldComponent` of a localized field: render the input of each locale with the children function of `LocalizedField`',\n { todo: true },\n );\n return;\n }\n const component = value.expression.name;\n const target = bindings.get(component);\n const input = j.identifier('input');\n const member = (prop: string) => j.memberExpression(input, j.identifier(prop));\n const prop = (prop: string, from = prop) => {\n const expression =\n prop === 'value' && api.typescript && target?.valueType !== undefined\n ? j.tsAsExpression(\n member(from),\n j.tsUnionType([\n target.valueType === 'string'\n ? j.tsStringKeyword()\n : target.valueType === 'number'\n ? j.tsNumberKeyword()\n : j.tsBooleanKeyword(),\n j.tsNullKeyword(),\n ]),\n )\n : member(from);\n return j.jsxAttribute(j.jsxIdentifier(prop), j.jsxExpressionContainer(expression));\n };\n const field = j.jsxElement(\n j.jsxOpeningElement(\n j.jsxIdentifier(component),\n [\n ...['label', 'value', 'onChange', 'onBlur', 'disabled', 'readOnly', 'required'].map(\n (name) => prop(name),\n ),\n // A component of the project read the locale of its value as `fieldLocale`.\n ...(target === undefined ? [prop('fieldLocale', 'locale')] : []),\n ],\n true,\n ),\n );\n element.openingElement.attributes = attributes.filter((candidate) => candidate !== attribute);\n element.openingElement.selfClosing = false;\n element.closingElement = j.jsxClosingElement(j.jsxIdentifier(name));\n element.children = [j.jsxExpressionContainer(j.arrowFunctionExpression([input], field))];\n}\n\n/** Renames, adds and reports the props of the elements of v5 components. */\nfunction convertElements(api: TransformApi, bindings: Map<string, FieldTarget>): void {\n const { j, root } = api;\n const noted = new Set<string>();\n root.find(j.JSXElement).forEach((path) => {\n const name = getElementName(path.node);\n const target = name !== null ? bindings.get(name) : undefined;\n if (name === null || target === undefined) {\n return;\n }\n if (target.name === 'LocalizedField') {\n convertFieldComponent(api, path, bindings);\n }\n const opening = path.node.openingElement;\n const renames = { ...FIELD_RENAMES, ...target.renames };\n const known = new Set([...FIELD_PROPS, ...target.props]);\n const unknown: string[] = [];\n (opening.attributes ?? []).forEach((attribute) => {\n const attributeName = getAttributeName(attribute);\n if (attributeName === null || attribute.type !== 'JSXAttribute') {\n return;\n }\n const renamed = renames[attributeName];\n if (renamed !== undefined) {\n attribute.name = j.jsxIdentifier(renamed);\n } else if (!known.has(attributeName)) {\n unknown.push(attributeName);\n }\n if (attributeName === 'label' && isMessageDescriptor(api, attribute)) {\n api.report(\n path,\n 'field-component',\n `\\`<${name} label>\\` is a message descriptor: labels of Panneau 5 are nodes, format it (\\`intl.formatMessage()\\`)`,\n { todo: true },\n );\n }\n });\n Object.entries(target.adds ?? {}).forEach(([prop, value]) => {\n if (\n (opening.attributes ?? []).some((attribute) => getAttributeName(attribute) === prop)\n ) {\n return;\n }\n const attributeValue =\n value === true\n ? null\n : typeof value === 'string'\n ? jsxString(j, value)\n : j.jsxExpressionContainer(\n j.arrayExpression(value.map((item) => j.stringLiteral(item))),\n );\n opening.attributes = [\n ...(opening.attributes ?? []),\n j.jsxAttribute(j.jsxIdentifier(prop), attributeValue),\n ];\n });\n if (\n target.name === 'LocalizedField' &&\n !(opening.attributes ?? []).some((a) => getAttributeName(a) === 'locales')\n ) {\n api.report(\n path,\n 'field-component',\n '`<LocalizedField>` needs `locales`: `useFieldsContext().locales` of `@panneau/fields` gives those of the form',\n { todo: true },\n );\n }\n if (unknown.length > 0) {\n api.report(\n path,\n 'field-component',\n `\\`<${name}>\\`: ${unknown.map((prop) => `\\`${prop}\\``).join(', ')} ${unknown.length > 1 ? 'are not props' : 'is not a prop'} of \\`${target.name}\\` of \\`${target.source}\\``,\n { todo: true },\n );\n }\n if (target.note !== undefined && !noted.has(name)) {\n noted.add(name);\n api.report(\n path,\n 'field-component',\n `\\`<${name}>\\` is \\`${target.name}\\` of \\`${target.source}\\`: ${target.note}`,\n {\n todo: true,\n },\n );\n }\n });\n}\n\n/**\n * `<FormGroup label>` around one v5 field becomes the field's `label`: v5 fields render their own\n * label, help and errors. Around anything else, it becomes a `Fieldset` of `@panneau/ui`.\n */\nfunction convertFormGroups(api: TransformApi, bindings: Map<string, FieldTarget>): void {\n const { j, root } = api;\n findImports(api, '@panneau/element-form-group')\n .find(j.ImportDefaultSpecifier)\n .forEach((specifier) => {\n const local = specifier.node.local?.name;\n if (typeof local !== 'string') {\n return;\n }\n const formGroup = local;\n let fieldset: string | null = null;\n root.find(j.JSXElement, { openingElement: { name: { name: formGroup } } }).forEach(\n (path) => {\n const attributes = path.node.openingElement.attributes ?? [];\n const children = (path.node.children ?? []).filter(\n (child) => !(child.type === 'JSXText' && child.value.trim() === ''),\n );\n const [child] = children;\n const childName = child?.type === 'JSXElement' ? getElementName(child) : null;\n const others = attributes.filter((attribute) => {\n const name = getAttributeName(attribute);\n return (\n name === null ||\n !['label', 'helpText', 'className', 'key'].includes(name)\n );\n });\n if (others.length > 0) {\n api.report(\n path,\n 'form-group',\n `\\`<${formGroup}>\\`: ${others.map((attribute) => `\\`${getAttributeName(attribute) ?? '...'}\\``).join(', ')} ${others.length > 1 ? 'have' : 'has'} no equivalent in Panneau 5`,\n { todo: true },\n );\n }\n const kept = attributes.filter((attribute) => !others.includes(attribute));\n if (\n children.length === 1 &&\n child?.type === 'JSXElement' &&\n childName !== null &&\n bindings.has(childName)\n ) {\n const childAttributes = child.openingElement.attributes ?? [];\n const has = (name: string) =>\n childAttributes.some(\n (attribute) => getAttributeName(attribute) === name,\n );\n const moved = kept.flatMap((attribute) => {\n const name = getAttributeName(attribute);\n const target = name === 'helpText' ? 'description' : name;\n if (\n attribute.type !== 'JSXAttribute' ||\n target === null ||\n has(target)\n ) {\n if (name === 'className' && attribute.type === 'JSXAttribute') {\n api.report(\n path,\n 'form-group',\n `\\`<${formGroup} className>\\` is dropped: \\`<${childName}>\\` has its own \\`className\\``,\n { todo: true },\n );\n }\n return [];\n }\n return [j.jsxAttribute(j.jsxIdentifier(target), attribute.value)];\n });\n child.openingElement.attributes = [...moved, ...childAttributes];\n j(path).replaceWith(child);\n return;\n }\n fieldset ??= addNamedImport(api, '@panneau/ui', 'Fieldset');\n path.node.openingElement.name = j.jsxIdentifier(fieldset);\n if (path.node.closingElement) {\n path.node.closingElement.name = j.jsxIdentifier(fieldset);\n }\n path.node.openingElement.attributes = attributes.map((attribute) => {\n const name = getAttributeName(attribute);\n if (\n attribute.type === 'JSXAttribute' &&\n (name === 'label' || name === 'helpText')\n ) {\n attribute.name = j.jsxIdentifier(\n name === 'label' ? 'legend' : 'description',\n );\n }\n return attribute;\n });\n },\n );\n removeUnusedImports(api, [formGroup]);\n });\n}\n\n/** `useFormValue()` reads the values of the form of `@panneau/forms`. */\nfunction convertFormValue(api: TransformApi): void {\n const { j, root } = api;\n const hook = getImportedName(api, CONTEXTS, 'useFormValue');\n if (hook === null) {\n return;\n }\n root.find(j.CallExpression, { callee: { type: 'Identifier', name: hook } }).forEach(\n (path: ASTPath<CallExpression>) => {\n if (path.node.arguments.length > 0) {\n api.report(\n path,\n 'form-value',\n '`useFormValue(path)`: `useFormContext().getValue(path)` of `@panneau/forms` reads a value of the form',\n { todo: true },\n );\n return;\n }\n const useFormState = addNamedImport(api, '@panneau/forms', 'useFormState');\n const useFormContext = addNamedImport(api, '@panneau/forms', 'useFormContext');\n j(path).replaceWith(\n j.memberExpression(\n j.callExpression(j.identifier(useFormState), [\n j.callExpression(j.identifier(useFormContext), []),\n ]),\n j.identifier('values'),\n ),\n );\n },\n );\n removeUnusedImports(api, [hook]);\n}\n\nexport function transformFields(api: TransformApi): void {\n const bindings = new Map<string, FieldTarget>();\n collectHookBindings(api, bindings);\n collectImportBindings(api, bindings);\n bindings.forEach((target, local) => {\n addNamedImport(api, target.source, target.name, { local });\n });\n convertElements(api, bindings);\n convertFormGroups(api, bindings);\n convertFormValue(api);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/transforms/imports.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,YAAY,CAAC;AAuD7D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CA+CxD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { removeImport } from "../context.js";
|
|
2
|
+
import { REPLACEMENTS, SHARED_PACKAGES, isV4Package } from "../mappings.js";
|
|
3
|
+
//#region src/transforms/imports.ts
|
|
4
|
+
/** Modules of v4 that v5 drops: their CSS and their locales. */
|
|
5
|
+
function isDropped(source) {
|
|
6
|
+
return /^@panneau\/[a-z-]+\/assets\/.*\.(css|scss)$/.test(source) || /^@panneau\/intl\/locale\//.test(source);
|
|
7
|
+
}
|
|
8
|
+
/** Subpaths of packages that v5 also has, which only v4 exported. */
|
|
9
|
+
var V4_SUBPATHS = /^@panneau\/(core\/(contexts|hooks|utils|lib|types)|app\/assets|intl\/locale)(\/|$)/;
|
|
10
|
+
/** Names that other transforms rewrite, and report when they can't. */
|
|
11
|
+
var HANDLED = { "@panneau/core/contexts": ["useFieldComponent", "useFormValue"] };
|
|
12
|
+
/** The package of a module: `@panneau/core` for `@panneau/core/contexts`. */
|
|
13
|
+
function getPackage(source) {
|
|
14
|
+
return source.split("/").slice(0, 2).join("/");
|
|
15
|
+
}
|
|
16
|
+
/** The names a module imports from a v4 module, with `default` for its default export. */
|
|
17
|
+
function getV4Names(declaration, source) {
|
|
18
|
+
const names = (declaration.specifiers ?? []).map((specifier) => specifier.type === "ImportSpecifier" ? String(specifier.imported.name) : specifier.type === "ImportDefaultSpecifier" ? "default" : "*");
|
|
19
|
+
const pkg = getPackage(source);
|
|
20
|
+
const shared = SHARED_PACKAGES[pkg];
|
|
21
|
+
if (isV4Package(pkg) || V4_SUBPATHS.test(source)) {
|
|
22
|
+
const handled = HANDLED[source] ?? [];
|
|
23
|
+
return names.length > 0 ? names.filter((name) => !handled.includes(name)) : "all";
|
|
24
|
+
}
|
|
25
|
+
if (shared !== void 0 && source === pkg) return names.filter((name) => shared.includes(name));
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
function describe(source, name) {
|
|
29
|
+
const replacement = REPLACEMENTS[source]?.[name] ?? REPLACEMENTS[getPackage(source)]?.[name];
|
|
30
|
+
const imported = name === "default" ? `the default export of \`${source}\`` : `\`${name}\` of \`${source}\``;
|
|
31
|
+
return replacement !== void 0 ? `${imported}: ${replacement}` : `${imported} has no equivalent in Panneau 5: see the migration guide`;
|
|
32
|
+
}
|
|
33
|
+
function transformImports(api) {
|
|
34
|
+
const { j, root } = api;
|
|
35
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
36
|
+
const source = String(path.node.source.value);
|
|
37
|
+
if (!source.startsWith("@panneau/")) return;
|
|
38
|
+
if (isDropped(source)) {
|
|
39
|
+
removeImport(api, path);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const names = getV4Names(path.node, source);
|
|
43
|
+
if (names === "all") {
|
|
44
|
+
api.report(path, "import", `\`${source}\` has no equivalent in Panneau 5: see the migration guide`, { todo: true });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
names.forEach((name) => {
|
|
48
|
+
api.report(path, "import", describe(source, name), { todo: true });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
root.find(j.CallExpression, { callee: { type: "Import" } }).forEach((path) => {
|
|
52
|
+
const [argument] = path.node.arguments;
|
|
53
|
+
const value = argument?.type === "StringLiteral" || argument?.type === "Literal" ? String(argument.value) : argument?.type === "TemplateLiteral" ? argument.quasis.map((quasi) => quasi.value.raw).join("*") : "";
|
|
54
|
+
if (value.startsWith("@panneau/")) api.report(path, "import", isDropped(value) ? `\`import('${value}')\`: messages of Panneau 5 come with its packages and with the \`locales\` of plugins; drop it` : `\`import('${value}')\` has no equivalent in Panneau 5: see the migration guide`, { todo: true });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { transformImports };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=imports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.js","names":[],"sources":["../../src/transforms/imports.ts"],"sourcesContent":["// Imports of `@panneau/*` that the other transforms left: stylesheets and locales of v4 are\n// dropped, since v5 packages import their own CSS and messages; the rest is reported with what\n// replaces it, and stays, so that the build fails until it is replaced.\nimport type { ASTPath, ImportDeclaration } from 'jscodeshift';\n\nimport { type TransformApi, removeImport } from '../context';\nimport { REPLACEMENTS, SHARED_PACKAGES, isV4Package } from '../mappings';\n\n/** Modules of v4 that v5 drops: their CSS and their locales. */\nfunction isDropped(source: string): boolean {\n return (\n /^@panneau\\/[a-z-]+\\/assets\\/.*\\.(css|scss)$/.test(source) ||\n /^@panneau\\/intl\\/locale\\//.test(source)\n );\n}\n\n/** Subpaths of packages that v5 also has, which only v4 exported. */\nconst V4_SUBPATHS =\n /^@panneau\\/(core\\/(contexts|hooks|utils|lib|types)|app\\/assets|intl\\/locale)(\\/|$)/;\n\n/** Names that other transforms rewrite, and report when they can't. */\nconst HANDLED: Readonly<Record<string, readonly string[]>> = {\n '@panneau/core/contexts': ['useFieldComponent', 'useFormValue'],\n};\n\n/** The package of a module: `@panneau/core` for `@panneau/core/contexts`. */\nfunction getPackage(source: string): string {\n return source.split('/').slice(0, 2).join('/');\n}\n\n/** The names a module imports from a v4 module, with `default` for its default export. */\nfunction getV4Names(declaration: ImportDeclaration, source: string): string[] | 'all' {\n const names = (declaration.specifiers ?? []).map((specifier) =>\n specifier.type === 'ImportSpecifier'\n ? String(specifier.imported.name)\n : specifier.type === 'ImportDefaultSpecifier'\n ? 'default'\n : '*',\n );\n const pkg = getPackage(source);\n const shared = SHARED_PACKAGES[pkg];\n if (isV4Package(pkg) || V4_SUBPATHS.test(source)) {\n const handled = HANDLED[source] ?? [];\n return names.length > 0 ? names.filter((name) => !handled.includes(name)) : 'all';\n }\n if (shared !== undefined && source === pkg) {\n return names.filter((name) => shared.includes(name));\n }\n return [];\n}\n\nfunction describe(source: string, name: string): string {\n const replacement = REPLACEMENTS[source]?.[name] ?? REPLACEMENTS[getPackage(source)]?.[name];\n const imported =\n name === 'default' ? `the default export of \\`${source}\\`` : `\\`${name}\\` of \\`${source}\\``;\n return replacement !== undefined\n ? `${imported}: ${replacement}`\n : `${imported} has no equivalent in Panneau 5: see the migration guide`;\n}\n\nexport function transformImports(api: TransformApi): void {\n const { j, root } = api;\n root.find(j.ImportDeclaration).forEach((path: ASTPath<ImportDeclaration>) => {\n const source = String(path.node.source.value);\n if (!source.startsWith('@panneau/')) {\n return;\n }\n if (isDropped(source)) {\n removeImport(api, path);\n return;\n }\n const names = getV4Names(path.node, source);\n if (names === 'all') {\n api.report(\n path,\n 'import',\n `\\`${source}\\` has no equivalent in Panneau 5: see the migration guide`,\n {\n todo: true,\n },\n );\n return;\n }\n names.forEach((name) => {\n api.report(path, 'import', describe(source, name), { todo: true });\n });\n });\n // `import('@panneau/intl/locale/fr')` loaded the messages of v4.\n root.find(j.CallExpression, { callee: { type: 'Import' } }).forEach((path) => {\n const [argument] = path.node.arguments;\n const value =\n argument?.type === 'StringLiteral' || argument?.type === 'Literal'\n ? String(argument.value)\n : argument?.type === 'TemplateLiteral'\n ? argument.quasis.map((quasi) => quasi.value.raw).join('*')\n : '';\n if (value.startsWith('@panneau/')) {\n api.report(\n path,\n 'import',\n isDropped(value)\n ? `\\`import('${value}')\\`: messages of Panneau 5 come with its packages and with the \\`locales\\` of plugins; drop it`\n : `\\`import('${value}')\\` has no equivalent in Panneau 5: see the migration guide`,\n { todo: true },\n );\n }\n });\n}\n"],"mappings":";;;;AASA,SAAS,UAAU,QAAyB;CACxC,OACI,8CAA8C,KAAK,MAAM,KACzD,4BAA4B,KAAK,MAAM;AAE/C;;AAGA,IAAM,cACF;;AAGJ,IAAM,UAAuD,EACzD,0BAA0B,CAAC,qBAAqB,cAAc,EAClE;;AAGA,SAAS,WAAW,QAAwB;CACxC,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;AACjD;;AAGA,SAAS,WAAW,aAAgC,QAAkC;CAClF,MAAM,SAAS,YAAY,cAAc,CAAC,EAAA,CAAG,KAAK,cAC9C,UAAU,SAAS,oBACb,OAAO,UAAU,SAAS,IAAI,IAC9B,UAAU,SAAS,2BACjB,YACA,GACZ;CACA,MAAM,MAAM,WAAW,MAAM;CAC7B,MAAM,SAAS,gBAAgB;CAC/B,IAAI,YAAY,GAAG,KAAK,YAAY,KAAK,MAAM,GAAG;EAC9C,MAAM,UAAU,QAAQ,WAAW,CAAC;EACpC,OAAO,MAAM,SAAS,IAAI,MAAM,QAAQ,SAAS,CAAC,QAAQ,SAAS,IAAI,CAAC,IAAI;CAChF;CACA,IAAI,WAAW,KAAA,KAAa,WAAW,KACnC,OAAO,MAAM,QAAQ,SAAS,OAAO,SAAS,IAAI,CAAC;CAEvD,OAAO,CAAC;AACZ;AAEA,SAAS,SAAS,QAAgB,MAAsB;CACpD,MAAM,cAAc,aAAa,OAAO,GAAG,SAAS,aAAa,WAAW,MAAM,EAAE,GAAG;CACvF,MAAM,WACF,SAAS,YAAY,2BAA2B,OAAO,MAAM,KAAK,KAAK,UAAU,OAAO;CAC5F,OAAO,gBAAgB,KAAA,IACjB,GAAG,SAAS,IAAI,gBAChB,GAAG,SAAS;AACtB;AAEA,SAAgB,iBAAiB,KAAyB;CACtD,MAAM,EAAE,GAAG,SAAS;CACpB,KAAK,KAAK,EAAE,iBAAiB,CAAC,CAAC,SAAS,SAAqC;EACzE,MAAM,SAAS,OAAO,KAAK,KAAK,OAAO,KAAK;EAC5C,IAAI,CAAC,OAAO,WAAW,WAAW,GAC9B;EAEJ,IAAI,UAAU,MAAM,GAAG;GACnB,aAAa,KAAK,IAAI;GACtB;EACJ;EACA,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM;EAC1C,IAAI,UAAU,OAAO;GACjB,IAAI,OACA,MACA,UACA,KAAK,OAAO,6DACZ,EACI,MAAM,KACV,CACJ;GACA;EACJ;EACA,MAAM,SAAS,SAAS;GACpB,IAAI,OAAO,MAAM,UAAU,SAAS,QAAQ,IAAI,GAAG,EAAE,MAAM,KAAK,CAAC;EACrE,CAAC;CACL,CAAC;CAED,KAAK,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,SAAS;EAC1E,MAAM,CAAC,YAAY,KAAK,KAAK;EAC7B,MAAM,QACF,UAAU,SAAS,mBAAmB,UAAU,SAAS,YACnD,OAAO,SAAS,KAAK,IACrB,UAAU,SAAS,oBACjB,SAAS,OAAO,KAAK,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,IACxD;EACZ,IAAI,MAAM,WAAW,WAAW,GAC5B,IAAI,OACA,MACA,UACA,UAAU,KAAK,IACT,aAAa,MAAM,mGACnB,aAAa,MAAM,+DACzB,EAAE,MAAM,KAAK,CACjB;CAER,CAAC;AACL"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@panneau/codemods",
|
|
3
|
+
"version": "5.0.0-alpha.7",
|
|
4
|
+
"description": "Codemods that move Panneau 4 projects to Panneau 5: the app entry, imports and custom field components, with a report of what they can't rewrite.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/folkloreinc/panneau-js.git",
|
|
10
|
+
"directory": "packages/codemods"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"@panneau/source": "./src/index.ts",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"bin"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"panneau-codemods": "./bin/panneau-codemods.js"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"api": "tsc -p tsconfig.build.json && node ../../scripts/api-report.js"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"jscodeshift": "^17.4.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/jscodeshift": "^17.3.0"
|
|
44
|
+
}
|
|
45
|
+
}
|