Skip to content

Commit

Permalink
[C3] fix: make sure that C3 doesn't delete pre-existing options in So…
Browse files Browse the repository at this point in the history
…lid's `defineConfig` call (#5954)

* [C3] fix: make sure that C3 doesn't delete pre-existing options in Solid's `defineConfig` call

* C3: reuse the new `mergeObjectProperties` function in nuxt as well
  • Loading branch information
dario-piotrowicz authored Jun 7, 2024
1 parent 38b5c16 commit 3b99e63
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-bottles-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": patch
---

fix: make sure that C3 doesn't delete pre-existing options in the `defineConfig` call
134 changes: 134 additions & 0 deletions packages/create-cloudflare/src/helpers/__tests__/codemod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { mergeObjectProperties } from "helpers/codemod";
import * as recast from "recast";
import parser from "recast/parsers/babel";
import { describe, expect, test } from "vitest";

describe("mergeObjectProperties", () => {
const tests = [
{
testName: "merges simple objects",
sourcePropertiesObject: {
propA: "_A_",
},
newPropertiesObject: {
propB: "_B_",
},
expectedPropertiesObject: {
propA: "_A_",
propB: "_B_",
},
},
{
testName: "overrides existing non-object properties",
sourcePropertiesObject: {
__Prop0: true,
propA: "_A_",
propB: false,
propC: 123,
},
newPropertiesObject: {
propA: "_a_",
propB: true,
propC: 456,
},
expectedPropertiesObject: {
__Prop0: true,
propA: "_a_",
propB: true,
propC: 456,
},
},
{
testName: "deep merges object properties",
sourcePropertiesObject: {
propA: {
propAA: "a",
propAB: "b",
propAC: {
propAA: {
propAAA: "this is quite nested 1",
propAAC: {
propAAAA: "this is even more nested",
},
},
},
},
},
newPropertiesObject: {
propA: {
propAB: "B",
propAC: {
propAA: {
propAAB: "this is quite nested 2",
propAAC: {
propAAAA: "this is even more nested 1",
propAAAB: "this is even more nested 2",
},
},
},
},
},
expectedPropertiesObject: {
propA: {
propAA: "a",
propAB: "B",
propAC: {
propAA: {
propAAA: "this is quite nested 1",
propAAC: {
propAAAA: "this is even more nested 1",
propAAAB: "this is even more nested 2",
},
propAAB: "this is quite nested 2",
},
},
},
},
},
] satisfies {
testName: string;
sourcePropertiesObject: Record<string, unknown>;
newPropertiesObject: Record<string, unknown>;
expectedPropertiesObject: Record<string, unknown>;
}[];

tests.forEach(({ testName, ...testObjects }) =>
test(testName, () => testMergeObjectProperties(testObjects)),
);
});

const testMergeObjectProperties = ({
sourcePropertiesObject,
newPropertiesObject,
expectedPropertiesObject,
}: {
sourcePropertiesObject: Record<string, unknown>;
newPropertiesObject: Record<string, unknown>;
expectedPropertiesObject: Record<string, unknown>;
}) => {
const sourceObj = createObjectExpression(sourcePropertiesObject);
const newProperties = createObjectExpression(newPropertiesObject)
.properties as recast.types.namedTypes.ObjectProperty[];
const expectedObj = createObjectExpression(expectedPropertiesObject);

mergeObjectProperties(sourceObj, newProperties);

expect(recast.prettyPrint(sourceObj, { parser }).code).toEqual(
recast.prettyPrint(expectedObj, { parser }).code,
);
};

const createObjectExpression = (
sourceObj: Record<string, unknown>,
): recast.types.namedTypes.ObjectExpression => {
return (
(
recast.parse(
`const obj = {${Object.entries(sourceObj)
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join(",\n")}}`,
{ parser },
).program.body[0] as recast.types.namedTypes.VariableDeclaration
).declarations[0] as recast.types.namedTypes.VariableDeclarator
).init as recast.types.namedTypes.ObjectExpression;
};
50 changes: 50 additions & 0 deletions packages/create-cloudflare/src/helpers/codemod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,53 @@ export const loadSnippets = (parentFolder: string) => {
export const loadTemplateSnippets = (ctx: C3Context) => {
return loadSnippets(getTemplatePath(ctx));
};

/**
* merges provided properties into a given object (updating the object itself), deeply merging them in case
* some properties are object themselves
*
* @param sourceObject the object into which merge the new properties
* @param newProperties the new properties to add/merge
*/
export const mergeObjectProperties = (
sourceObject: recast.types.namedTypes.ObjectExpression,
newProperties: recast.types.namedTypes.ObjectProperty[],
): void => {
newProperties.forEach((newProp) => {
const newPropName = getPropertyName(newProp);
if (!newPropName) {
return false;
}
const indexOfExisting = sourceObject.properties.findIndex(
(p) => p.type === "ObjectProperty" && getPropertyName(p) === newPropName,
);

const existing = sourceObject.properties[indexOfExisting];
if (!existing) {
sourceObject.properties.push(newProp);
return;
}

if (
existing.type === "ObjectProperty" &&
existing.value.type === "ObjectExpression" &&
newProp.value.type === "ObjectExpression"
) {
mergeObjectProperties(
existing.value,
newProp.value.properties as recast.types.namedTypes.ObjectProperty[],
);
return;
}

sourceObject.properties[indexOfExisting] = newProp;
});
};

const getPropertyName = (newProp: recast.types.namedTypes.ObjectProperty) => {
return newProp.key.type === "Identifier"
? newProp.key.name
: newProp.key.type === "StringLiteral"
? newProp.key.value
: null;
};
11 changes: 5 additions & 6 deletions packages/create-cloudflare/templates/nuxt/c3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { logRaw } from "@cloudflare/cli";
import { brandColor, dim } from "@cloudflare/cli/colors";
import { spinner } from "@cloudflare/cli/interactive";
import { runFrameworkGenerator } from "frameworks/index";
import { transformFile } from "helpers/codemod";
import { mergeObjectProperties, transformFile } from "helpers/codemod";
import { getLatestTypesEntrypoint } from "helpers/compatDate";
import { readFile, writeFile } from "helpers/files";
import { detectPackageManager } from "helpers/packageManagers";
Expand Down Expand Up @@ -96,11 +96,10 @@ const updateNuxtConfig = () => {
visitCallExpression: function (n) {
const callee = n.node.callee as recast.types.namedTypes.Identifier;
if (callee.name === "defineNuxtConfig") {
const obj = n.node
.arguments[0] as recast.types.namedTypes.ObjectExpression;

obj.properties.push(presetDef);
obj.properties.push(moduleDef);
mergeObjectProperties(
n.node.arguments[0] as recast.types.namedTypes.ObjectExpression,
[presetDef, moduleDef],
);
}

return this.traverse(n);
Expand Down
12 changes: 7 additions & 5 deletions packages/create-cloudflare/templates/solid/c3.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { logRaw, updateStatus } from "@cloudflare/cli";
import { blue } from "@cloudflare/cli/colors";
import { runFrameworkGenerator } from "frameworks/index";
import { transformFile } from "helpers/codemod";
import { mergeObjectProperties, transformFile } from "helpers/codemod";
import { usesTypescript } from "helpers/files";
import { detectPackageManager } from "helpers/packageManagers";
import * as recast from "recast";
Expand Down Expand Up @@ -32,8 +32,10 @@ const configure = async (ctx: C3Context) => {
}

const b = recast.types.builders;
n.node.arguments = [
b.objectExpression([

mergeObjectProperties(
n.node.arguments[0] as recast.types.namedTypes.ObjectExpression,
[
b.objectProperty(
b.identifier("server"),
b.objectExpression([
Expand All @@ -52,8 +54,8 @@ const configure = async (ctx: C3Context) => {
),
]),
),
]),
];
],
);

return false;
},
Expand Down

0 comments on commit 3b99e63

Please sign in to comment.