From 0d20d15a5723fa2a14bd8dced936a7c2cacca75a Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Mon, 26 Jan 2026 18:38:33 -0300 Subject: [PATCH 1/7] wip --- package.json | 3 +- .../javascript/utils/getImportLocalName.ts | 8 +++++ src/application/project/sdk/javasScriptSdk.ts | 31 +++++++++++++++- .../project/sdk/storyblookPlugin.ts | 36 +++++++++++++++++++ .../utils/getImportLocalName.test.ts | 36 +++++++++++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/application/project/sdk/storyblookPlugin.ts diff --git a/package.json b/package.json index ccccc523..ce2f47f6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "test": "jest -c jest.config.js --coverage", "validate": "tsc --noEmit", "build": "tsup", - "graphql-codegen": "graphql-codegen --config codegen.ts" + "graphql-codegen": "graphql-codegen --config codegen.ts", + "postinstall": "graphql-codegen" }, "dependencies": { "@babel/core": "^7.28.5", diff --git a/src/application/project/code/transformation/javascript/utils/getImportLocalName.ts b/src/application/project/code/transformation/javascript/utils/getImportLocalName.ts index f28f36d7..efe50180 100644 --- a/src/application/project/code/transformation/javascript/utils/getImportLocalName.ts +++ b/src/application/project/code/transformation/javascript/utils/getImportLocalName.ts @@ -33,6 +33,14 @@ export function getImportLocalName(source: string | t.File, matcher: ImportMatch continue; } + if (t.isImportNamespaceSpecifier(specifier)) { + if (matches('*', matcher.importName)) { + localName = specifier.local.name; + } + + continue; + } + if (t.isImportSpecifier(specifier) && matches(specifier.imported, matcher.importName)) { localName = specifier.local.name; diff --git a/src/application/project/sdk/javasScriptSdk.ts b/src/application/project/sdk/javasScriptSdk.ts index 404f2712..449f037b 100644 --- a/src/application/project/sdk/javasScriptSdk.ts +++ b/src/application/project/sdk/javasScriptSdk.ts @@ -36,6 +36,7 @@ export type Configuration = { formatter: CodeFormatter, fileSystem: FileSystem, tsConfigLoader: TsConfigLoader, + hooks?: JavaScriptSdkPlugin[], }; type VersionedContent = { @@ -49,6 +50,10 @@ type ContentOptions = BaseContentOptions & { notifier?: TaskNotifier, }; +export type JavaScriptSdkPlugin = { + getInstallationPlan(installation: Installation): Promise>, +}; + export abstract class JavaScriptSdk implements Sdk { protected static readonly CONTENT_PACKAGE = '@croct/content'; @@ -64,6 +69,8 @@ export abstract class JavaScriptSdk implements Sdk { private readonly importConfigLoader: TsConfigLoader; + private readonly plugins: JavaScriptSdkPlugin[]; + protected constructor(configuration: Configuration) { this.projectDirectory = configuration.projectDirectory; this.packageManager = configuration.packageManager; @@ -71,6 +78,7 @@ export abstract class JavaScriptSdk implements Sdk { this.formatter = configuration.formatter; this.fileSystem = configuration.fileSystem; this.importConfigLoader = configuration.tsConfigLoader; + this.plugins = configuration.hooks ?? []; } public async generateSlotExample(slot: Slot, installation: Installation): Promise { @@ -99,7 +107,7 @@ export abstract class JavaScriptSdk implements Sdk { public async setup(installation: Installation): Promise { const {input, output} = installation; - const plan = await this.getInstallationPlan(installation); + const plan = await this.resolveInstallationPlan(installation); const configuration: ProjectConfiguration = { ...plan.configuration, @@ -279,6 +287,27 @@ export abstract class JavaScriptSdk implements Sdk { return defaultPath; } + private resolveInstallationPlan(installation: Installation): Promise { + let promise = this.getInstallationPlan(installation); + + for (const plugin of this.plugins) { + promise = promise.then(async plan => { + const hookPlan = await plugin(installation); + + return { + tasks: [...plan.tasks, ...(hookPlan.tasks ?? [])], + dependencies: [...plan.dependencies, ...(hookPlan.dependencies ?? [])], + configuration: { + ...plan.configuration, + ...hookPlan.configuration, + }, + }; + }); + } + + return promise; + } + protected abstract getInstallationPlan(installation: Installation): Promise; public async update(installation: Installation, options: UpdateOptions = {}): Promise { diff --git a/src/application/project/sdk/storyblookPlugin.ts b/src/application/project/sdk/storyblookPlugin.ts new file mode 100644 index 00000000..3c6f9edb --- /dev/null +++ b/src/application/project/sdk/storyblookPlugin.ts @@ -0,0 +1,36 @@ +import {InstallationPlan, JavaScriptSdkPlugin} from '@/application/project/sdk/javasScriptSdk'; +import {Installation} from '@/application/project/sdk/sdk'; +import {PackageManager} from '@/application/project/packageManager/packageManager'; +import {Task} from '@/application/cli/io/output'; +import {HelpfulError} from '@/application/error'; + +export class StoryblookPlugin implements JavaScriptSdkPlugin { + private packageManager: PackageManager; + + public async getInstallationPlan(_: Installation): Promise> { + if (!await this.packageManager.hasDependency('@storyblok/js')) { + return {}; + } + + const tasks: Task[] = []; + + tasks.push({ + title: 'Configuring Storyblok integration', + task: async notifier => { + notifier.update('Configuring middleware'); + + try { + await this.updateCode(this.codemod.middleware, installation.project.middleware.file); + + notifier.confirm('Storyblok configured'); + } catch (error) { + notifier.alert('Failed to configure Storyblok', HelpfulError.formatMessage(error)); + } + }, + }); + + return { + dependencies: ['@croct/plug-storyblok'], + }; + } +} diff --git a/test/application/project/code/transformation/javascript/utils/getImportLocalName.test.ts b/test/application/project/code/transformation/javascript/utils/getImportLocalName.test.ts index 2b82454c..e90f010f 100644 --- a/test/application/project/code/transformation/javascript/utils/getImportLocalName.test.ts +++ b/test/application/project/code/transformation/javascript/utils/getImportLocalName.test.ts @@ -93,6 +93,42 @@ describe('getImportLocalName', () => { }, expected: 'alias', }, + { + description: 'return the local name of the namespace import', + code: 'import * as sdk from \'croct\';', + matcher: { + moduleName: 'croct', + importName: '*', + }, + expected: 'sdk', + }, + { + description: 'return null if namespace import does not match the module name', + code: 'import * as sdk from \'croct\';', + matcher: { + moduleName: 'something', + importName: '*', + }, + expected: null, + }, + { + description: 'return null if looking for namespace but import is named', + code: 'import {sdk} from \'croct\';', + matcher: { + moduleName: 'croct', + importName: '*', + }, + expected: null, + }, + { + description: 'return the local name of the namespace import when module matches regex', + code: 'import * as croct from \'@croct/sdk\';', + matcher: { + moduleName: /@croct/, + importName: '*', + }, + expected: 'croct', + }, ])('should $description', ({code, matcher, expected}) => { expect(getImportLocalName(code, matcher)).toBe(expected); }); From 10b016b91bc6269bba624baf046b150661afccb1 Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Tue, 27 Jan 2026 12:00:31 -0300 Subject: [PATCH 2/7] Add support for Storyblok SDK --- .../javascript/storyblokInitCodemod.ts | 97 ++++++++ src/application/project/sdk/javasScriptSdk.ts | 22 +- .../project/sdk/storyblookPlugin.ts | 67 +++++- src/infrastructure/application/cli/cli.ts | 33 ++- .../javascript/storyblokInitCodemod.test.ts | 221 ++++++++++++++++++ 5 files changed, 428 insertions(+), 12 deletions(-) create mode 100644 src/application/project/code/transformation/javascript/storyblokInitCodemod.ts create mode 100644 test/application/project/code/transformation/javascript/storyblokInitCodemod.test.ts diff --git a/src/application/project/code/transformation/javascript/storyblokInitCodemod.ts b/src/application/project/code/transformation/javascript/storyblokInitCodemod.ts new file mode 100644 index 00000000..251f0608 --- /dev/null +++ b/src/application/project/code/transformation/javascript/storyblokInitCodemod.ts @@ -0,0 +1,97 @@ +/* eslint-disable no-param-reassign -- False positives */ +import * as t from '@babel/types'; +import {traverse} from '@babel/core'; +import {Codemod, CodemodOptions, ResultCode} from '@/application/project/code/transformation/codemod'; +import {addImport} from '@/application/project/code/transformation/javascript/utils/addImport'; +import {getImportLocalName} from '@/application/project/code/transformation/javascript/utils/getImportLocalName'; + +export type StoryblokInitCodemodOptions = CodemodOptions & { + name: string, + module: string, +}; + +export class StoryblokInitCodemod implements Codemod { + public apply(input: t.File, options?: StoryblokInitCodemodOptions): Promise> { + if (options === undefined) { + return Promise.resolve({ + modified: false, + result: input, + }); + } + + const localName = getImportLocalName(input, { + moduleName: /@storyblok\/(js|react)/, + importName: /storyblokInit|\*/, + }); + + if (localName === null) { + return Promise.resolve({ + modified: false, + result: input, + }); + } + + const importLocalName = options.module !== undefined + ? getImportLocalName(input, { + importName: options.name, + moduleName: options.module, + }) + : null; + + const wrapperName = importLocalName ?? options.name; + let modified = false; + + traverse(input, { + CallExpression: path => { + const {callee} = path.node; + + // Match direct function calls: targetFunction(...) + if (t.isIdentifier(callee) && callee.name === 'storyblokInit') { + path.node.arguments = [ + t.callExpression( + t.identifier(wrapperName), + path.node.arguments, + ), + ]; + + modified = true; + } + + // Match member expression calls: obj.targetFunction(...) + if ( + t.isMemberExpression(callee) + && t.isIdentifier(callee.property) + && callee.property.name === 'storyblokInit' + ) { + path.node.arguments = [ + t.callExpression( + t.identifier(wrapperName), + path.node.arguments, + ), + ]; + + modified = true; + } + }, + }); + + if (modified && importLocalName === null) { + const {body} = input.program; + + if (!t.isImportDeclaration(body[0])) { + body.unshift(t.emptyStatement()); + } + + addImport(input, { + type: 'value', + moduleName: options.module, + importName: options.name, + }); + } + + return Promise.resolve({ + modified: modified, + result: input, + }); + } +} diff --git a/src/application/project/sdk/javasScriptSdk.ts b/src/application/project/sdk/javasScriptSdk.ts index 449f037b..7727f646 100644 --- a/src/application/project/sdk/javasScriptSdk.ts +++ b/src/application/project/sdk/javasScriptSdk.ts @@ -36,7 +36,7 @@ export type Configuration = { formatter: CodeFormatter, fileSystem: FileSystem, tsConfigLoader: TsConfigLoader, - hooks?: JavaScriptSdkPlugin[], + plugins?: JavaScriptSdkPlugin[], }; type VersionedContent = { @@ -50,8 +50,17 @@ type ContentOptions = BaseContentOptions & { notifier?: TaskNotifier, }; +export type JavaScriptPluginContext = { + packageManager: PackageManager, + projectDirectory: WorkingDirectory, + fileSystem: FileSystem, +}; + export type JavaScriptSdkPlugin = { - getInstallationPlan(installation: Installation): Promise>, + getInstallationPlan( + installation: Installation, + context: JavaScriptPluginContext + ): Promise>, }; export abstract class JavaScriptSdk implements Sdk { @@ -78,7 +87,7 @@ export abstract class JavaScriptSdk implements Sdk { this.formatter = configuration.formatter; this.fileSystem = configuration.fileSystem; this.importConfigLoader = configuration.tsConfigLoader; - this.plugins = configuration.hooks ?? []; + this.plugins = configuration.plugins ?? []; } public async generateSlotExample(slot: Slot, installation: Installation): Promise { @@ -289,10 +298,15 @@ export abstract class JavaScriptSdk implements Sdk { private resolveInstallationPlan(installation: Installation): Promise { let promise = this.getInstallationPlan(installation); + const context: JavaScriptPluginContext = { + packageManager: this.packageManager, + projectDirectory: this.projectDirectory, + fileSystem: this.fileSystem, + }; for (const plugin of this.plugins) { promise = promise.then(async plan => { - const hookPlan = await plugin(installation); + const hookPlan = await plugin.getInstallationPlan(installation, context); return { tasks: [...plan.tasks, ...(hookPlan.tasks ?? [])], diff --git a/src/application/project/sdk/storyblookPlugin.ts b/src/application/project/sdk/storyblookPlugin.ts index 3c6f9edb..2afa9ee1 100644 --- a/src/application/project/sdk/storyblookPlugin.ts +++ b/src/application/project/sdk/storyblookPlugin.ts @@ -1,14 +1,22 @@ -import {InstallationPlan, JavaScriptSdkPlugin} from '@/application/project/sdk/javasScriptSdk'; -import {Installation} from '@/application/project/sdk/sdk'; -import {PackageManager} from '@/application/project/packageManager/packageManager'; +import {extname} from 'path'; +import {InstallationPlan, JavaScriptSdkPlugin, JavaScriptPluginContext} from '@/application/project/sdk/javasScriptSdk'; import {Task} from '@/application/cli/io/output'; import {HelpfulError} from '@/application/error'; +import {Codemod} from '@/application/project/code/transformation/codemod'; +import {Installation} from '@/application/project/sdk/sdk'; export class StoryblookPlugin implements JavaScriptSdkPlugin { - private packageManager: PackageManager; + private readonly codemod: Codemod; + + public constructor(codemod: Codemod) { + this.codemod = codemod; + } - public async getInstallationPlan(_: Installation): Promise> { - if (!await this.packageManager.hasDependency('@storyblok/js')) { + public async getInstallationPlan( + _: Installation, + context: JavaScriptPluginContext, + ): Promise> { + if (!await context.packageManager.hasDependency('@storyblok/js')) { return {}; } @@ -20,7 +28,7 @@ export class StoryblookPlugin implements JavaScriptSdkPlugin { notifier.update('Configuring middleware'); try { - await this.updateCode(this.codemod.middleware, installation.project.middleware.file); + await this.configureStoryblok(context); notifier.confirm('Storyblok configured'); } catch (error) { @@ -30,7 +38,52 @@ export class StoryblookPlugin implements JavaScriptSdkPlugin { }); return { + tasks: tasks, dependencies: ['@croct/plug-storyblok'], }; } + + private async configureStoryblok(scope: JavaScriptPluginContext): Promise { + const initializationFile = await this.findStoryblokInitializationFile(scope); + + if (initializationFile === null) { + throw new HelpfulError('Could not find any file containing Storyblok initialization.'); + } + + const result = await this.codemod.apply(initializationFile); + + if (!result.modified) { + throw new HelpfulError('Unable to automatically configure Storyblok integration.'); + } + } + + private async findStoryblokInitializationFile(scope: JavaScriptPluginContext): Promise { + const {fileSystem, projectDirectory} = scope; + + const directory = projectDirectory.get(); + const iterator = fileSystem.list(directory, (path, depth) => { + if (depth > 20) { + return false; + } + + const extension = extname(path).toLowerCase(); + + return extension === '.js' || extension === '.ts' || extension === '.jsx' || extension === '.tsx'; + }); + + for await (const entry of iterator) { + if (entry.type !== 'file') { + continue; + } + + const path = fileSystem.joinPaths(directory, entry.name); + const content = await fileSystem.readTextFile(path); + + if (content.includes('storyblokInit')) { + return path; + } + } + + return null; + } } diff --git a/src/infrastructure/application/cli/cli.ts b/src/infrastructure/application/cli/cli.ts index 33618783..4922582b 100644 --- a/src/infrastructure/application/cli/cli.ts +++ b/src/infrastructure/application/cli/cli.ts @@ -7,6 +7,7 @@ import XDGAppPaths from 'xdg-app-paths'; import ci from 'ci-info'; import {FilteredLogger, Logger, LogLevel} from '@croct/logging'; import {Token} from '@croct/sdk/token'; +import {File} from '@babel/types'; import {ConsoleInput} from '@/infrastructure/application/cli/io/consoleInput'; import {ConsoleOutput, LinkOpener} from '@/infrastructure/application/cli/io/consoleOutput'; import {Sdk} from '@/application/project/sdk/sdk'; @@ -312,7 +313,7 @@ import {WriteFileOptionsValidator} from '@/infrastructure/application/validation import {AutoUpdater} from '@/application/cli/autoUpdater'; import {DeletePathAction} from '@/application/template/action/deletePathAction'; import {DeletePathOptionsValidator} from '@/infrastructure/application/validation/actions/deletePathOptionsValidator'; -import {Codemod} from '@/application/project/code/transformation/codemod'; +import {Codemod, ResultCode} from '@/application/project/code/transformation/codemod'; import {ResolveImportAction} from '@/application/template/action/resolveImportAction'; import { ResolveImportOptionsValidator, @@ -321,6 +322,8 @@ import {CreateApiKeyAction} from '@/application/template/action/createApiKeyActi import { CreateApiKeyOptionsValidator, } from '@/infrastructure/application/validation/actions/createApiKeyOptionsValidator'; +import {StoryblokInitCodemod} from '@/application/project/code/transformation/javascript/storyblokInitCodemod'; +import {StoryblookPlugin} from '@/application/project/sdk/storyblookPlugin'; export type Configuration = { program: Program, @@ -1727,10 +1730,12 @@ export class Cli { mapping: { [Platform.JAVASCRIPT]: (): Sdk => new PlugJsSdk({ ...config, + plugins: [new StoryblookPlugin(this.createStoryblokCodemod(Platform.JAVASCRIPT))], bundlers: ['vite', 'parcel', 'tsup', 'rollup'], }), [Platform.REACT]: (): Sdk => new PlugReactSdk({ ...config, + plugins: [new StoryblookPlugin(this.createStoryblokCodemod(Platform.REACT))], importResolver: importResolver, codemod: { provider: new FormatCodemod( @@ -1790,6 +1795,7 @@ export class Cli { return new PlugNextSdk({ ...config, + plugins: [new StoryblookPlugin(this.createStoryblokCodemod(Platform.NEXTJS))], userApi: this.getUserApi(), applicationApi: this.getApplicationApi(), importResolver: importResolver, @@ -1923,6 +1929,31 @@ export class Cli { }); } + private createStoryblokCodemod(platform: Platform.JAVASCRIPT | Platform.REACT | Platform.NEXTJS): Codemod { + const codemod = new StoryblokInitCodemod(); + const modules = { + [Platform.JAVASCRIPT]: 'js', + [Platform.REACT]: 'react', + [Platform.NEXTJS]: 'next', + }; + + return new FormatCodemod( + this.getJavaScriptFormatter(), + new FileCodemod({ + fileSystem: this.getFileSystem(), + codemod: new JavaScriptCodemod({ + languages: ['typescript', 'jsx'], + codemod: { + apply: (input: File): Promise> => codemod.apply(input, { + name: 'withCroct', + module: `@croct/plug-storyblok/${modules[platform]}`, + }), + }, + }), + }), + ); + } + private share any)>(method: M, factory: () => ReturnType): ReturnType { const instance = this.instances.get(method); diff --git a/test/application/project/code/transformation/javascript/storyblokInitCodemod.test.ts b/test/application/project/code/transformation/javascript/storyblokInitCodemod.test.ts new file mode 100644 index 00000000..7ebd38aa --- /dev/null +++ b/test/application/project/code/transformation/javascript/storyblokInitCodemod.test.ts @@ -0,0 +1,221 @@ +import { + StoryblokInitCodemod, + StoryblokInitCodemodOptions, +} from '@/application/project/code/transformation/javascript/storyblokInitCodemod'; +import {JavaScriptCodemod} from '@/application/project/code/transformation/javascript/javaScriptCodemod'; + +describe('StoryblokInitCodemod', () => { + function createTransformer(): JavaScriptCodemod { + return new JavaScriptCodemod({ + languages: ['typescript', 'jsx'], + codemod: new StoryblokInitCodemod(), + }); + } + + it('should wrap storyblokInit arguments when imported from @storyblok/js', async () => { + const transformer = createTransformer(); + + const input = [ + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toEqual([ + 'import { withCroct } from "@croct/storyblok";', + "import { storyblokInit } from '@storyblok/js';", + 'storyblokInit(withCroct({ accessToken: "token" }));', + ].join('\n')); + }); + + it('should wrap storyblokInit arguments when imported from @storyblok/react', async () => { + const transformer = createTransformer(); + + const input = [ + "import { storyblokInit } from '@storyblok/react';", + '', + 'storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toEqual([ + 'import { withCroct } from "@croct/storyblok";', + "import { storyblokInit } from '@storyblok/react';", + 'storyblokInit(withCroct({ accessToken: "token" }));', + ].join('\n')); + }); + + it('should wrap member expression calls', async () => { + const transformer = createTransformer(); + + const input = [ + "import * as sb from '@storyblok/js';", + '', + 'sb.storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toEqual([ + 'import { withCroct } from "@croct/storyblok";', + "import * as sb from '@storyblok/js';", + 'sb.storyblokInit(withCroct({ accessToken: "token" }));', + ].join('\n')); + }); + + it('should use existing import alias for wrapper function', async () => { + const transformer = createTransformer(); + + const input = [ + "import { withCroct as croctWrapper } from '@croct/storyblok';", + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toEqual([ + "import { withCroct as croctWrapper } from '@croct/storyblok';", + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit(croctWrapper({ accessToken: "token" }));', + ].join('\n')); + }); + + it('should not modify code when storyblokInit import is not found', async () => { + const transformer = createTransformer(); + + const input = [ + "import { someOtherFunction } from '@storyblok/js';", + '', + 'someOtherFunction({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(false); + expect(result).toBe(input); + }); + + it('should not modify code when no storyblok import exists', async () => { + const transformer = createTransformer(); + + const input = [ + "import { something } from 'other-module';", + '', + 'storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(false); + expect(result).toBe(input); + }); + + it('should return unmodified when options are not provided', async () => { + const transformer = createTransformer(); + + const input = [ + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input); + + expect(modified).toBe(false); + expect(result).toBe(input); + }); + + it('should wrap multiple storyblokInit calls', async () => { + const transformer = createTransformer(); + + const input = [ + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit({ accessToken: "token1" });', + 'storyblokInit({ accessToken: "token2" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toEqual([ + 'import { withCroct } from "@croct/storyblok";', + "import { storyblokInit } from '@storyblok/js';", + 'storyblokInit(withCroct({ accessToken: "token1" }));', + 'storyblokInit(withCroct({ accessToken: "token2" }));', + ].join('\n')); + }); + + it('should handle storyblokInit with multiple arguments', async () => { + const transformer = createTransformer(); + + const input = [ + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit(config, options);', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toEqual([ + 'import { withCroct } from "@croct/storyblok";', + "import { storyblokInit } from '@storyblok/js';", + 'storyblokInit(withCroct(config, options));', + ].join('\n')); + }); + + it('should add empty statement before import when first statement is not an import', async () => { + const transformer = createTransformer(); + + const input = [ + 'const x = 1;', + "import { storyblokInit } from '@storyblok/js';", + '', + 'storyblokInit({ accessToken: "token" });', + ].join('\n'); + + const {result, modified} = await transformer.apply(input, { + name: 'withCroct', + module: '@croct/storyblok', + }); + + expect(modified).toBe(true); + expect(result).toContain('import { withCroct } from "@croct/storyblok";'); + expect(result).toContain('storyblokInit(withCroct({ accessToken: "token" }));'); + }); +}); From aed38e0db90aac3690a6e4e171867eeec3406773 Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Tue, 27 Jan 2026 12:02:48 -0300 Subject: [PATCH 3/7] Fix vulnerabilities --- package-lock.json | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07726eed..790b93b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "croct", "version": "0.0.0-dev", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", @@ -2531,10 +2532,11 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -10474,10 +10476,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -10778,10 +10781,11 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -13286,10 +13290,11 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", From b17a7075bef2034d3b0a6eb4f667920693954bf3 Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Tue, 27 Jan 2026 12:21:23 -0300 Subject: [PATCH 4/7] Fix issues --- .../project/sdk/storyblookPlugin.ts | 50 +++++++++++++------ src/infrastructure/application/cli/cli.ts | 46 ++++++++++------- 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/application/project/sdk/storyblookPlugin.ts b/src/application/project/sdk/storyblookPlugin.ts index 2afa9ee1..ad64c7d7 100644 --- a/src/application/project/sdk/storyblookPlugin.ts +++ b/src/application/project/sdk/storyblookPlugin.ts @@ -4,12 +4,21 @@ import {Task} from '@/application/cli/io/output'; import {HelpfulError} from '@/application/error'; import {Codemod} from '@/application/project/code/transformation/codemod'; import {Installation} from '@/application/project/sdk/sdk'; +import {ScanFilter} from '@/application/fs/fileSystem'; + +export type Configuration = { + scanFilter: ScanFilter, + codemod: Codemod, +}; export class StoryblookPlugin implements JavaScriptSdkPlugin { private readonly codemod: Codemod; - public constructor(codemod: Codemod) { - this.codemod = codemod; + private readonly scanFilter: ScanFilter; + + public constructor(configuration: Configuration) { + this.codemod = configuration.codemod; + this.scanFilter = configuration.scanFilter; } public async getInstallationPlan( @@ -23,9 +32,9 @@ export class StoryblookPlugin implements JavaScriptSdkPlugin { const tasks: Task[] = []; tasks.push({ - title: 'Configuring Storyblok integration', + title: 'Configure Storyblok integration', task: async notifier => { - notifier.update('Configuring middleware'); + notifier.update('Configuring Storyblok integration'); try { await this.configureStoryblok(context); @@ -44,33 +53,44 @@ export class StoryblookPlugin implements JavaScriptSdkPlugin { } private async configureStoryblok(scope: JavaScriptPluginContext): Promise { - const initializationFile = await this.findStoryblokInitializationFile(scope); + const initializationFiles = await this.findStoryblokInitializationFiles(scope); - if (initializationFile === null) { + if (initializationFiles.length === 0) { throw new HelpfulError('Could not find any file containing Storyblok initialization.'); } - const result = await this.codemod.apply(initializationFile); + const results = initializationFiles.map( + file => this.codemod + .apply(file) + .then(result => result.modified), + ); - if (!result.modified) { - throw new HelpfulError('Unable to automatically configure Storyblok integration.'); + if (!(await Promise.all(results)).some(modified => modified)) { + throw new HelpfulError('Could not find any Storyblok initialization to configure.'); } } - private async findStoryblokInitializationFile(scope: JavaScriptPluginContext): Promise { + private async findStoryblokInitializationFiles(scope: JavaScriptPluginContext): Promise { const {fileSystem, projectDirectory} = scope; const directory = projectDirectory.get(); - const iterator = fileSystem.list(directory, (path, depth) => { - if (depth > 20) { + const iterator = fileSystem.list(directory, async (path, depth) => { + if (!await this.scanFilter(path, depth) || depth > 20) { return false; } const extension = extname(path).toLowerCase(); - return extension === '.js' || extension === '.ts' || extension === '.jsx' || extension === '.tsx'; + // Allow directories (no extension) and JS/TS files + return extension === '' + || extension === '.js' + || extension === '.ts' + || extension === '.jsx' + || extension === '.tsx'; }); + const files: string[] = []; + for await (const entry of iterator) { if (entry.type !== 'file') { continue; @@ -80,10 +100,10 @@ export class StoryblookPlugin implements JavaScriptSdkPlugin { const content = await fileSystem.readTextFile(path); if (content.includes('storyblokInit')) { - return path; + files.push(path); } } - return null; + return files; } } diff --git a/src/infrastructure/application/cli/cli.ts b/src/infrastructure/application/cli/cli.ts index 4922582b..92884cf7 100644 --- a/src/infrastructure/application/cli/cli.ts +++ b/src/infrastructure/application/cli/cli.ts @@ -11,7 +11,10 @@ import {File} from '@babel/types'; import {ConsoleInput} from '@/infrastructure/application/cli/io/consoleInput'; import {ConsoleOutput, LinkOpener} from '@/infrastructure/application/cli/io/consoleOutput'; import {Sdk} from '@/application/project/sdk/sdk'; -import {Configuration as JavaScriptSdkConfiguration} from '@/application/project/sdk/javasScriptSdk'; +import { + Configuration as JavaScriptSdkConfiguration, + JavaScriptSdkPlugin, +} from '@/application/project/sdk/javasScriptSdk'; import {PlugJsSdk} from '@/application/project/sdk/plugJsSdk'; import {PlugReactSdk} from '@/application/project/sdk/plugReactSdk'; import {PlugNextSdk} from '@/application/project/sdk/plugNextSdk'; @@ -1730,12 +1733,12 @@ export class Cli { mapping: { [Platform.JAVASCRIPT]: (): Sdk => new PlugJsSdk({ ...config, - plugins: [new StoryblookPlugin(this.createStoryblokCodemod(Platform.JAVASCRIPT))], + plugins: [this.createStoryblokPlugin(Platform.JAVASCRIPT)], bundlers: ['vite', 'parcel', 'tsup', 'rollup'], }), [Platform.REACT]: (): Sdk => new PlugReactSdk({ ...config, - plugins: [new StoryblookPlugin(this.createStoryblokCodemod(Platform.REACT))], + plugins: [this.createStoryblokPlugin(Platform.REACT)], importResolver: importResolver, codemod: { provider: new FormatCodemod( @@ -1795,7 +1798,7 @@ export class Cli { return new PlugNextSdk({ ...config, - plugins: [new StoryblookPlugin(this.createStoryblokCodemod(Platform.NEXTJS))], + plugins: [this.createStoryblokPlugin(Platform.NEXTJS)], userApi: this.getUserApi(), applicationApi: this.getApplicationApi(), importResolver: importResolver, @@ -1929,7 +1932,9 @@ export class Cli { }); } - private createStoryblokCodemod(platform: Platform.JAVASCRIPT | Platform.REACT | Platform.NEXTJS): Codemod { + private createStoryblokPlugin( + platform: Platform.JAVASCRIPT | Platform.REACT | Platform.NEXTJS, + ): JavaScriptSdkPlugin { const codemod = new StoryblokInitCodemod(); const modules = { [Platform.JAVASCRIPT]: 'js', @@ -1937,21 +1942,24 @@ export class Cli { [Platform.NEXTJS]: 'next', }; - return new FormatCodemod( - this.getJavaScriptFormatter(), - new FileCodemod({ - fileSystem: this.getFileSystem(), - codemod: new JavaScriptCodemod({ - languages: ['typescript', 'jsx'], - codemod: { - apply: (input: File): Promise> => codemod.apply(input, { - name: 'withCroct', - module: `@croct/plug-storyblok/${modules[platform]}`, - }), - }, + return new StoryblookPlugin({ + scanFilter: this.getScanFilter(), + codemod: new FormatCodemod( + this.getJavaScriptFormatter(), + new FileCodemod({ + fileSystem: this.getFileSystem(), + codemod: new JavaScriptCodemod({ + languages: ['typescript', 'jsx'], + codemod: { + apply: (input: File): Promise> => codemod.apply(input, { + name: 'withCroct', + module: `@croct/plug-storyblok/${modules[platform]}`, + }), + }, + }), }), - }), - ); + ), + }); } private share any)>(method: M, factory: () => ReturnType): ReturnType { From badc849be5f4d94e624d690f68f8f472986ce509 Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Tue, 27 Jan 2026 13:15:01 -0300 Subject: [PATCH 5/7] Apply review suggestions --- .../project/sdk/{storyblookPlugin.ts => storyblokPlugin.ts} | 2 +- src/infrastructure/application/cli/cli.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/application/project/sdk/{storyblookPlugin.ts => storyblokPlugin.ts} (98%) diff --git a/src/application/project/sdk/storyblookPlugin.ts b/src/application/project/sdk/storyblokPlugin.ts similarity index 98% rename from src/application/project/sdk/storyblookPlugin.ts rename to src/application/project/sdk/storyblokPlugin.ts index ad64c7d7..51ff12c3 100644 --- a/src/application/project/sdk/storyblookPlugin.ts +++ b/src/application/project/sdk/storyblokPlugin.ts @@ -11,7 +11,7 @@ export type Configuration = { codemod: Codemod, }; -export class StoryblookPlugin implements JavaScriptSdkPlugin { +export class StoryblokPlugin implements JavaScriptSdkPlugin { private readonly codemod: Codemod; private readonly scanFilter: ScanFilter; diff --git a/src/infrastructure/application/cli/cli.ts b/src/infrastructure/application/cli/cli.ts index 92884cf7..00d7e034 100644 --- a/src/infrastructure/application/cli/cli.ts +++ b/src/infrastructure/application/cli/cli.ts @@ -326,7 +326,7 @@ import { CreateApiKeyOptionsValidator, } from '@/infrastructure/application/validation/actions/createApiKeyOptionsValidator'; import {StoryblokInitCodemod} from '@/application/project/code/transformation/javascript/storyblokInitCodemod'; -import {StoryblookPlugin} from '@/application/project/sdk/storyblookPlugin'; +import {StoryblokPlugin} from '@/application/project/sdk/storyblokPlugin'; export type Configuration = { program: Program, @@ -1942,7 +1942,7 @@ export class Cli { [Platform.NEXTJS]: 'next', }; - return new StoryblookPlugin({ + return new StoryblokPlugin({ scanFilter: this.getScanFilter(), codemod: new FormatCodemod( this.getJavaScriptFormatter(), From a88f03e66e9331ecc19f747b5675b5adbdd4ab35 Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Tue, 27 Jan 2026 14:52:12 -0300 Subject: [PATCH 6/7] Add Next.js 16 proxy compatibility --- ...lewareCodemod.ts => nextJsProxyCodemod.ts} | 212 +++---- src/application/project/sdk/plugNextSdk.ts | 34 +- src/infrastructure/application/cli/cli.ts | 44 +- .../project/javaScriptFormatter.ts | 12 +- .../defaultExportArrowFunction.ts | 3 - .../nextjs-middleware/defaultExportClass.ts | 3 - .../existingAliasedHofCall.ts | 5 - .../existingAliasedMiddlewareCall.ts | 5 - .../nextjs-middleware/existingHofCall.ts | 5 - .../nextjs-middleware/existingImport.ts | 9 - .../existingImportAliased.ts | 9 - .../existingMiddlewareCall.ts | 5 - .../existingMiddlewareReexport.ts | 1 - .../nextjs-middleware/matcherAlias.ts | 7 - .../namedExportArrowFunction.ts | 1 - .../namedExportArrowFunctionWithBody.ts | 3 - .../namedExportFunctionDeclaration.ts | 3 - .../namedExportFunctionExpression.ts | 3 - .../nextjs-middleware/namedSpecifiedExport.ts | 9 - .../configAfterDefaultExport.ts | 2 +- .../configAfterNamedExport.ts | 2 +- .../configAfterProxyWithReference.ts} | 4 +- .../configInvalidReference.ts | 2 +- .../configWithArrayMatcher.ts | 2 +- .../configWithIndirectVariableReference.ts | 2 +- .../configWithStringMatcher.ts | 2 +- .../configWithVariableMatcher.ts | 2 +- .../configWithVariableReference.ts | 2 +- .../configWithoutMatcher.ts | 2 +- .../defaultExportAnonymousFunction.ts | 2 +- .../defaultExportArrowFunction.ts | 3 + .../defaultExportArrowFunctionReference.ts | 0 ...ultExportArrowFunctionWithBodyReference.ts | 0 .../nextjs-proxy/defaultExportClass.ts | 3 + .../defaultExportFunctionDeclaration.ts | 0 ...faultExportFunctionDeclarationReference.ts | 0 ...efaultExportFunctionExpressionReference.ts | 0 .../defaultExportIndirectReference.ts | 0 .../empty.ts | 0 .../nextjs-proxy/existingAliasedHofCall.ts | 5 + .../nextjs-proxy/existingAliasedProxyCall.ts | 5 + .../existingConfig.ts | 0 .../existingConfigArrayMatcher.ts | 0 .../existingConfigMatcher.ts | 0 .../fixtures/nextjs-proxy/existingHofCall.ts | 5 + .../fixtures/nextjs-proxy/existingImport.ts | 9 + .../nextjs-proxy/existingImportAliased.ts | 9 + .../nextjs-proxy/existingProxyCall.ts | 5 + .../nextjs-proxy/existingProxyReexport.ts | 1 + .../fixtures/nextjs-proxy/matcherAlias.ts | 7 + .../nextjs-proxy/namedExportArrowFunction.ts | 1 + .../namedExportArrowFunctionWithBody.ts | 3 + .../namedExportFunctionDeclaration.ts | 3 + .../namedExportFunctionExpression.ts | 3 + .../nextjs-proxy/namedSpecifiedExport.ts | 9 + .../specifiedExportWithAliases.ts | 6 +- .../unrelatedExports.ts | 2 +- .../nextJsMiddlewareCodemod.test.ts.snap | 577 ------------------ ...mod.test.ts => nextJsProxyCodemod.test.ts} | 23 +- 59 files changed, 263 insertions(+), 813 deletions(-) rename src/application/project/code/transformation/javascript/{nextJsMiddlewareCodemod.ts => nextJsProxyCodemod.ts} (76%) delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunction.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportClass.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedHofCall.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedMiddlewareCall.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingHofCall.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingImport.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingImportAliased.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareCall.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareReexport.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/matcherAlias.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunction.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunctionWithBody.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionDeclaration.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionExpression.ts delete mode 100644 test/application/project/code/transformation/fixtures/nextjs-middleware/namedSpecifiedExport.ts rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configAfterDefaultExport.ts (75%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configAfterNamedExport.ts (72%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware/configAfterMiddlewareWithReference.ts => nextjs-proxy/configAfterProxyWithReference.ts} (90%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configInvalidReference.ts (62%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configWithArrayMatcher.ts (75%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configWithIndirectVariableReference.ts (85%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configWithStringMatcher.ts (76%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configWithVariableMatcher.ts (79%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configWithVariableReference.ts (82%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/configWithoutMatcher.ts (60%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportAnonymousFunction.ts (50%) create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunction.ts rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportArrowFunctionReference.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportArrowFunctionWithBodyReference.ts (100%) create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportClass.ts rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportFunctionDeclaration.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportFunctionDeclarationReference.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportFunctionExpressionReference.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/defaultExportIndirectReference.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/empty.ts (100%) create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedHofCall.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedProxyCall.ts rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/existingConfig.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/existingConfigArrayMatcher.ts (100%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/existingConfigMatcher.ts (100%) create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingHofCall.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingImport.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingImportAliased.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyCall.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyReexport.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/matcherAlias.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunction.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunctionWithBody.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionDeclaration.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionExpression.ts create mode 100644 test/application/project/code/transformation/fixtures/nextjs-proxy/namedSpecifiedExport.ts rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/specifiedExportWithAliases.ts (51%) rename test/application/project/code/transformation/fixtures/{nextjs-middleware => nextjs-proxy}/unrelatedExports.ts (64%) delete mode 100644 test/application/project/code/transformation/javascript/__snapshots__/nextJsMiddlewareCodemod.test.ts.snap rename test/application/project/code/transformation/javascript/{nextJsMiddlewareCodemod.test.ts => nextJsProxyCodemod.test.ts} (64%) diff --git a/src/application/project/code/transformation/javascript/nextJsMiddlewareCodemod.ts b/src/application/project/code/transformation/javascript/nextJsProxyCodemod.ts similarity index 76% rename from src/application/project/code/transformation/javascript/nextJsMiddlewareCodemod.ts rename to src/application/project/code/transformation/javascript/nextJsProxyCodemod.ts index 8e6858ce..5d1748f3 100644 --- a/src/application/project/code/transformation/javascript/nextJsMiddlewareCodemod.ts +++ b/src/application/project/code/transformation/javascript/nextJsProxyCodemod.ts @@ -19,39 +19,40 @@ type VariableMatch = { declaration: t.VariableDeclarator, }; -export type MiddlewareConfiguration = { +export type ProxyConfiguration = { matcherPattern: string, + exportName: string, import: { module: string, - middlewareFactoryName: string, - middlewareName: string, + proxyFactoryName: string, + proxyName: string, }, }; /** - * Refactors the middleware wrapping it with necessary configuration. + * Refactors the proxy wrapping it with necessary configuration. * - * This transformer wraps the existing middleware function with a higher-order - * function that provides the necessary configuration for the middleware to - * work correctly. It can also detect if the middleware is already configured + * This transformer wraps the existing proxy function with a higher-order + * function that provides the necessary configuration for the proxy to + * work correctly. It can also detect if the proxy is already configured * or missing configuration and apply the necessary changes. */ -export class NextJsMiddlewareCodemod implements Codemod { - private readonly configuration: MiddlewareConfiguration; +export class NextJsProxyCodemod implements Codemod { + private readonly configuration: ProxyConfiguration; - public constructor(options: MiddlewareConfiguration) { + public constructor(options: ProxyConfiguration) { this.configuration = options; } public apply(input: t.File): Promise> { const {body} = input.program; - const isMiddlewareReexported = hasReexport(input, { + const isProxyReexported = hasReexport(input, { moduleName: this.configuration.import.module, - importName: this.configuration.import.middlewareName, + importName: this.configuration.import.proxyName, }); - const localConfig = NextJsMiddlewareCodemod.findConfig(input); + const localConfig = NextJsProxyCodemod.findConfig(input); const addConfigExport = (): void => { // Add a configuration object with the matcher pattern @@ -70,10 +71,10 @@ export class NextJsMiddlewareCodemod implements Codemod { )); }; - if (isMiddlewareReexported) { + if (isProxyReexported) { if (localConfig !== null) { - // Middleware is re-exported and config found in the source code, - // consider the middleware configured + // Proxy is re-exported and config found in the source code, + // consider the proxy configured return Promise.resolve({ modified: false, result: input, @@ -89,48 +90,49 @@ export class NextJsMiddlewareCodemod implements Codemod { }); } - const middlewareFactoryName = getImportLocalName(input, { + const proxyFactoryName = getImportLocalName(input, { moduleName: this.configuration.import.module, - importName: this.configuration.import.middlewareFactoryName, + importName: this.configuration.import.proxyFactoryName, }); - const middlewareName = getImportLocalName(input, { + const proxyName = getImportLocalName(input, { moduleName: this.configuration.import.module, - importName: this.configuration.import.middlewareName, + importName: this.configuration.import.proxyName, }); const existingImports: string[] = []; - if (middlewareFactoryName !== null) { - existingImports.push(middlewareFactoryName); + if (proxyFactoryName !== null) { + existingImports.push(proxyFactoryName); } - if (middlewareName !== null) { - existingImports.push(middlewareName); + if (proxyName !== null) { + existingImports.push(proxyName); } - if (existingImports.length > 0 && NextJsMiddlewareCodemod.isCalled(input, existingImports)) { - // The middleware is already called in the source code, consider it refactored + if (existingImports.length > 0 && NextJsProxyCodemod.isCalled(input, existingImports)) { + // The proxy is already called in the source code, consider it refactored return Promise.resolve({ modified: false, result: input, }); } - let middlewareNode = NextJsMiddlewareCodemod.refactorMiddleware( + let proxyNode = NextJsProxyCodemod.refactorProxy( input, - middlewareFactoryName ?? this.configuration.import.middlewareFactoryName, + this.configuration.exportName, + proxyFactoryName ?? this.configuration.import.proxyFactoryName, localConfig !== null && localConfig.matcher ? localConfig.name : undefined, ); - if (middlewareNode === null) { + if (proxyNode === null) { if (localConfig === null) { - // No middleware found or configuration object, - // just add middleware re-export + // No proxy found or configuration object, + // just add proxy re-export addReexport(input, { type: 'value', moduleName: this.configuration.import.module, - importName: this.configuration.import.middlewareName, + importName: this.configuration.import.proxyName, }); addConfigExport(); @@ -141,10 +143,10 @@ export class NextJsMiddlewareCodemod implements Codemod { }); } - // Configurations found but no middleware, add the middleware - middlewareNode = t.exportDefaultDeclaration( + // Configurations found but no proxy, add the proxy + proxyNode = t.exportDefaultDeclaration( t.callExpression( - t.identifier(middlewareFactoryName ?? this.configuration.import.middlewareFactoryName), + t.identifier(proxyFactoryName ?? this.configuration.import.proxyFactoryName), [ t.objectExpression([ t.objectProperty( @@ -159,7 +161,7 @@ export class NextJsMiddlewareCodemod implements Codemod { ), ); - body.push(middlewareNode); + body.push(proxyNode); } if (localConfig !== null) { @@ -167,11 +169,11 @@ export class NextJsMiddlewareCodemod implements Codemod { this.configureMatcher(localConfig.object, this.configuration.matcherPattern); const configPosition = body.indexOf(localConfig.root as t.Statement); - const middlewarePosition = body.indexOf(middlewareNode as t.Statement); + const proxyPosition = body.indexOf(proxyNode as t.Statement); - if (configPosition > middlewarePosition) { + if (configPosition > proxyPosition) { /* - The middleware references the config object, so the config should be moved before it. + The proxy references the config object, so the config should be moved before it. Any variable or function used by the config object should be moved alongside it. The current logic handles most cases, including edge cases with multiple references. @@ -179,25 +181,25 @@ export class NextJsMiddlewareCodemod implements Codemod { and unlikely to occur as Next.js requires the config to be static for analysis at build time. */ - body.splice(middlewarePosition, 0, ...body.splice(configPosition, 1)); + body.splice(proxyPosition, 0, ...body.splice(configPosition, 1)); // Move any references of the config object alongside it - for (const reference of NextJsMiddlewareCodemod.findReferencesFrom(localConfig.root, input.program)) { + for (const reference of NextJsProxyCodemod.findReferencesFrom(localConfig.root, input.program)) { const referencePosition = body.indexOf(reference as t.Statement); - if (referencePosition > middlewarePosition) { - body.splice(middlewarePosition, 0, ...body.splice(referencePosition, 1)); + if (referencePosition > proxyPosition) { + body.splice(proxyPosition, 0, ...body.splice(referencePosition, 1)); } } } } - if (middlewareFactoryName === null) { - // If no import for the middleware factory was found, add it + if (proxyFactoryName === null) { + // If no import for the proxy factory was found, add it addImport(input, { type: 'value', moduleName: this.configuration.import.module, - importName: this.configuration.import.middlewareFactoryName, + importName: this.configuration.import.proxyFactoryName, }); } @@ -208,7 +210,7 @@ export class NextJsMiddlewareCodemod implements Codemod { } /** - * Adds the middleware matcher to the config object. + * Adds the proxy matcher to the config object. * * @param configObject The object expression representing the configuration. * @param pattern The pattern to add to the matcher. @@ -281,14 +283,20 @@ export class NextJsMiddlewareCodemod implements Codemod { } /** - * Refactors the middleware wrapping it with necessary configuration. + * Refactors the proxy wrapping it with necessary configuration. * * @param ast The AST representing the source code. - * @param functionName The name of the middleware function. + * @param exportName The name of the proxy identifier. + * @param functionName The name of the proxy function. * @param configName Optional name of the configuration object variable. - * @return The root node of the refactored middleware or null if not found. + * @return The root node of the refactored proxy or null if not found. */ - private static refactorMiddleware(ast: t.File, functionName: string, configName?: string): t.Node | null { + private static refactorProxy( + ast: t.File, + exportName: string, + functionName: string, + configName?: string, + ): t.Node | null { let rootNode: t.Node | null = null; traverse(ast, { @@ -296,21 +304,21 @@ export class NextJsMiddlewareCodemod implements Codemod { const {node} = path; const {declaration, specifiers = []} = node; - // export function middleware() {} + // export function proxy() {} if (t.isFunctionDeclaration(declaration)) { if ( t.isFunctionDeclaration(node.declaration) && t.isIdentifier(node.declaration.id) - && node.declaration.id.name === 'middleware' + && node.declaration.id.name === exportName ) { path.replaceWith( t.exportNamedDeclaration( t.variableDeclaration('const', [ t.variableDeclarator( - t.identifier('middleware'), - NextJsMiddlewareCodemod.wrapMiddleware( + t.identifier(exportName), + NextJsProxyCodemod.wrapProxy( t.isFunctionDeclaration(node.declaration) - ? NextJsMiddlewareCodemod.createFunctionExpression(node.declaration) + ? NextJsProxyCodemod.createFunctionExpression(node.declaration) : node.declaration, functionName, configName, @@ -321,7 +329,7 @@ export class NextJsMiddlewareCodemod implements Codemod { ), ); - rootNode = NextJsMiddlewareCodemod.getRootNode(path); + rootNode = NextJsProxyCodemod.getRootNode(path); return path.stop(); } @@ -329,23 +337,23 @@ export class NextJsMiddlewareCodemod implements Codemod { return path.skip(); } - // export const middleware = function() {} + // export const proxy = function() {} if (t.isVariableDeclaration(declaration)) { for (const declarator of declaration.declarations) { if ( t.isVariableDeclarator(declarator) && t.isIdentifier(declarator.id) - && declarator.id.name === 'middleware' + && declarator.id.name === exportName ) { const initializer = declarator.init ?? null; if (initializer !== null) { - declarator.init = NextJsMiddlewareCodemod.wrapMiddleware( + declarator.init = NextJsProxyCodemod.wrapProxy( initializer, functionName, configName, ); - rootNode = NextJsMiddlewareCodemod.getRootNode(path); + rootNode = NextJsProxyCodemod.getRootNode(path); return path.stop(); } @@ -353,15 +361,15 @@ export class NextJsMiddlewareCodemod implements Codemod { } } - // export {middleware} + // export {proxy} for (const specifier of specifiers) { if ( t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported) && t.isIdentifier(specifier.local) - && (['middleware', 'default']).includes(specifier.exported.name) + && ([exportName, 'default']).includes(specifier.exported.name) ) { - rootNode = NextJsMiddlewareCodemod.replaceMiddlewareDeclaration( + rootNode = NextJsProxyCodemod.replaceProxyDeclaration( ast, specifier.local.name, functionName, @@ -382,7 +390,7 @@ export class NextJsMiddlewareCodemod implements Codemod { if (t.isArrowFunctionExpression(declaration)) { path.replaceWith( t.exportDefaultDeclaration( - NextJsMiddlewareCodemod.wrapMiddleware( + NextJsProxyCodemod.wrapProxy( declaration, functionName, configName, @@ -390,7 +398,7 @@ export class NextJsMiddlewareCodemod implements Codemod { ), ); - rootNode = NextJsMiddlewareCodemod.getRootNode(path); + rootNode = NextJsProxyCodemod.getRootNode(path); return path.stop(); } @@ -399,22 +407,22 @@ export class NextJsMiddlewareCodemod implements Codemod { if (t.isFunctionDeclaration(declaration)) { path.replaceWith( t.exportDefaultDeclaration( - NextJsMiddlewareCodemod.wrapMiddleware( - NextJsMiddlewareCodemod.createFunctionExpression(declaration, true), + NextJsProxyCodemod.wrapProxy( + NextJsProxyCodemod.createFunctionExpression(declaration, true), functionName, configName, ), ), ); - rootNode = NextJsMiddlewareCodemod.getRootNode(path); + rootNode = NextJsProxyCodemod.getRootNode(path); return path.stop(); } - // export default middleware + // export default proxy if (t.isIdentifier(declaration)) { - rootNode = NextJsMiddlewareCodemod.replaceMiddlewareDeclaration( + rootNode = NextJsProxyCodemod.replaceProxyDeclaration( ast, declaration.name, functionName, @@ -431,7 +439,7 @@ export class NextJsMiddlewareCodemod implements Codemod { return rootNode; } - private static replaceMiddlewareDeclaration( + private static replaceProxyDeclaration( file: t.File, name: string, functionName: string, @@ -447,8 +455,8 @@ export class NextJsMiddlewareCodemod implements Codemod { const initializer = node.init ?? null; if (initializer !== null) { - node.init = NextJsMiddlewareCodemod.wrapMiddleware(initializer, functionName, configName); - rootNode = NextJsMiddlewareCodemod.getRootNode(path); + node.init = NextJsProxyCodemod.wrapProxy(initializer, functionName, configName); + rootNode = NextJsProxyCodemod.getRootNode(path); } return path.stop(); @@ -461,7 +469,7 @@ export class NextJsMiddlewareCodemod implements Codemod { if (t.isIdentifier(node.id) && node.id.name === name) { path.replaceWith( - NextJsMiddlewareCodemod.wrapFunctionDeclaration( + NextJsProxyCodemod.wrapFunctionDeclaration( node, functionName, configName, @@ -471,7 +479,7 @@ export class NextJsMiddlewareCodemod implements Codemod { ), ); - rootNode = NextJsMiddlewareCodemod.getRootNode(path); + rootNode = NextJsProxyCodemod.getRootNode(path); return path.stop(); } @@ -488,7 +496,7 @@ export class NextJsMiddlewareCodemod implements Codemod { * * @param file The AST representing the source code. * @param functionNames The names of the functions to search for. - * @return true if the middleware is called, false otherwise. + * @return true if the proxy is called, false otherwise. */ private static isCalled(file: t.File, functionNames: string[]): boolean { let wrapped = false; @@ -514,7 +522,7 @@ export class NextJsMiddlewareCodemod implements Codemod { } /** - * Finds the middleware configuration object in the t. + * Finds the proxy configuration object in the t. * * @param ast The AST representing the source code. * @return The information about the config object or null if not found. @@ -536,10 +544,10 @@ export class NextJsMiddlewareCodemod implements Codemod { ) { const match = t.isIdentifier(declarator.init) // export const config = variable - ? NextJsMiddlewareCodemod.findVariableDeclarator(ast, declarator.init.name) + ? NextJsProxyCodemod.findVariableDeclarator(ast, declarator.init.name) : { name: 'config', - root: NextJsMiddlewareCodemod.getRootNode(path), + root: NextJsProxyCodemod.getRootNode(path), declaration: declarator, }; @@ -552,7 +560,7 @@ export class NextJsMiddlewareCodemod implements Codemod { name: match.name, root: match.root, object: match.declaration.init, - matcher: NextJsMiddlewareCodemod.hasMatcherProperty(match.declaration.init), + matcher: NextJsProxyCodemod.hasMatcherProperty(match.declaration.init), }; return path.stop(); @@ -569,14 +577,14 @@ export class NextJsMiddlewareCodemod implements Codemod { && t.isIdentifier(specifier.local) && specifier.exported.name === 'config' ) { - const match = NextJsMiddlewareCodemod.findVariableDeclarator(ast, specifier.local.name); + const match = NextJsProxyCodemod.findVariableDeclarator(ast, specifier.local.name); if (match !== null && t.isObjectExpression(match.declaration.init)) { config = { name: match.name, root: match.root, object: match.declaration.init, - matcher: NextJsMiddlewareCodemod.hasMatcherProperty(match.declaration.init), + matcher: NextJsProxyCodemod.hasMatcherProperty(match.declaration.init), }; } @@ -639,11 +647,11 @@ export class NextJsMiddlewareCodemod implements Codemod { ) { if (t.isIdentifier(node.init)) { // If the initializer is an identifier, recursively search for the declaration - declarator = NextJsMiddlewareCodemod.findVariableDeclarator(ast, node.init.name); + declarator = NextJsProxyCodemod.findVariableDeclarator(ast, node.init.name); } else { declarator = { name: name, - root: NextJsMiddlewareCodemod.getRootNode(path), + root: NextJsProxyCodemod.getRootNode(path), declaration: node, }; } @@ -659,14 +667,14 @@ export class NextJsMiddlewareCodemod implements Codemod { } /** - * Wraps the given node with the HOC middleware. + * Wraps the given node with the HOC proxy. * - * @param node The node to wrap with the middleware. - * @param functionName The name of the middleware function. + * @param node The node to wrap with the proxy. + * @param functionName The name of the proxy function. * @param configName Optional name of the configuration object variable. - * @return The transformed middleware node. + * @return The transformed proxy node. */ - private static wrapMiddleware(node: t.Expression, functionName: string, configName?: string): t.CallExpression { + private static wrapProxy(node: t.Expression, functionName: string, configName?: string): t.CallExpression { return t.callExpression( t.identifier(functionName), [ @@ -690,27 +698,27 @@ export class NextJsMiddlewareCodemod implements Codemod { } /** - * Wraps a function declaration in a middleware expression as a variable declaration. + * Wraps a function declaration in a proxy expression as a variable declaration. * * @param functionDeclaration The function declaration to wrap. - * @param functionName The name of the middleware function. + * @param functionName The name of the proxy function. * @param configName Optional name of the configuration object variable. - * @param name The name of the constant variable to assign the middleware to. - * @return A variable declaration that assigns the wrapped middleware to a constant. + * @param name The name of the constant variable to assign the proxy to. + * @return A variable declaration that assigns the wrapped proxy to a constant. */ private static wrapFunctionDeclaration( functionDeclaration: t.FunctionDeclaration, functionName: string, configName?: string, - name = 'middleware', + name = 'proxy', ): t.VariableDeclaration { return t.variableDeclaration( 'const', [ t.variableDeclarator( t.identifier(name), - NextJsMiddlewareCodemod.wrapMiddleware( - NextJsMiddlewareCodemod.createFunctionExpression(functionDeclaration), + NextJsProxyCodemod.wrapProxy( + NextJsProxyCodemod.createFunctionExpression(functionDeclaration), functionName, configName, ), @@ -743,7 +751,7 @@ export class NextJsMiddlewareCodemod implements Codemod { Identifier: function acceptNested(nestedPath) { const identifier = nestedPath.node; - if (NextJsMiddlewareCodemod.isVariableReference(nestedPath.parent, identifier)) { + if (NextJsProxyCodemod.isVariableReference(nestedPath.parent, identifier)) { names.add(identifier.name); } @@ -767,7 +775,7 @@ export class NextJsMiddlewareCodemod implements Codemod { const {node} = path; if (t.isIdentifier(node.id) && names.has(node.id.name)) { - references.push(NextJsMiddlewareCodemod.getRootNode(path)); + references.push(NextJsProxyCodemod.getRootNode(path)); } return path.skip(); @@ -780,7 +788,7 @@ export class NextJsMiddlewareCodemod implements Codemod { const {node} = path; if (t.isIdentifier(node.id) && names.has(node.id.name)) { - references.push(NextJsMiddlewareCodemod.getRootNode(path)); + references.push(NextJsProxyCodemod.getRootNode(path)); } return path.skip(); @@ -793,7 +801,7 @@ export class NextJsMiddlewareCodemod implements Codemod { const {node} = path; if (t.isIdentifier(node.id) && names.has(node.id.name)) { - references.push(NextJsMiddlewareCodemod.getRootNode(path)); + references.push(NextJsProxyCodemod.getRootNode(path)); } return path.skip(); @@ -803,7 +811,7 @@ export class NextJsMiddlewareCodemod implements Codemod { return [ ...new Set(references.flatMap( // Recursively find references from the found references - reference => [reference, ...NextJsMiddlewareCodemod.findReferencesFrom(reference, root)], + reference => [reference, ...NextJsProxyCodemod.findReferencesFrom(reference, root)], )), ]; } diff --git a/src/application/project/sdk/plugNextSdk.ts b/src/application/project/sdk/plugNextSdk.ts index fc853af2..cc259762 100644 --- a/src/application/project/sdk/plugNextSdk.ts +++ b/src/application/project/sdk/plugNextSdk.ts @@ -28,6 +28,7 @@ import {ApiKeyPermission} from '@/application/model/application'; import {PlugReactExampleGenerator} from '@/application/project/code/generation/slot/plugReactExampleGenerator'; type CodemodConfiguration = { + proxy: Codemod, middleware: Codemod, fallbackProvider: Codemod, appRouterProvider: Codemod, @@ -50,7 +51,8 @@ type NextProjectInfo = { router: NextRouter, sourceDirectory: string, pageDirectory: string, - middleware: { + proxy: { + name: 'proxy' | 'middleware', file: string, }, provider: { @@ -181,10 +183,11 @@ export class PlugNextSdk extends JavaScriptSdk { } private async getProjectInfo(): Promise { - const [isTypescript, directory, fallbackMode] = await Promise.all([ + const [isTypescript, directory, fallbackMode, legacyMiddleware] = await Promise.all([ this.isTypeScriptProject(), this.getPageDirectory(), this.isFallbackMode(), + this.packageManager.hasDirectDependency('next', '<16'), ]); const project: Pick = { @@ -194,9 +197,11 @@ export class PlugNextSdk extends JavaScriptSdk { pageDirectory: directory, }; - const [middlewareFile, providerComponentFile] = await Promise.all([ + const proxyName = legacyMiddleware ? 'middleware' : 'proxy'; + + const [proxyFile, providerComponentFile] = await Promise.all([ this.locateFile( - ...['middleware.js', 'middleware.ts'] + ...[`${proxyName}.js`, `${proxyName}.ts`] .map(file => this.fileSystem.joinPaths(project.sourceDirectory, file)), ), this.locateFile( @@ -233,8 +238,9 @@ export class PlugNextSdk extends JavaScriptSdk { this.fileSystem.joinPaths(projectDirectory, '.env.production'), ), }, - middleware: { - file: middlewareFile ?? this.fileSystem.joinPaths(project.sourceDirectory, `middleware.${extension}`), + proxy: { + name: proxyName, + file: proxyFile ?? this.fileSystem.joinPaths(project.sourceDirectory, `${proxyName}.${extension}`), }, provider: { file: providerComponentFile @@ -251,17 +257,19 @@ export class PlugNextSdk extends JavaScriptSdk { const tasks: Task[] = []; if (!installation.project.fallbackMode) { + const proxyName = installation.project.proxy.name; + tasks.push({ - title: 'Configure middleware', + title: `Configure ${proxyName}`, task: async notifier => { - notifier.update('Configuring middleware'); + notifier.update(`Configuring ${proxyName}`); try { - await this.updateCode(this.codemod.middleware, installation.project.middleware.file); + await this.updateCode(this.codemod[proxyName], installation.project.proxy.file); - notifier.confirm('Middleware configured'); + notifier.confirm(`${PlugNextSdk.capitalize(proxyName)} configured`); } catch (error) { - notifier.alert('Failed to install middleware', HelpfulError.formatMessage(error)); + notifier.alert(`Failed to install ${proxyName}`, HelpfulError.formatMessage(error)); } }, }); @@ -440,4 +448,8 @@ export class PlugNextSdk extends JavaScriptSdk { private isFallbackMode(): Promise { return this.packageManager.hasDirectDependency('next', '<=13'); } + + private static capitalize(name: string): string { + return name.charAt(0).toUpperCase() + name.slice(1); + } } diff --git a/src/infrastructure/application/cli/cli.ts b/src/infrastructure/application/cli/cli.ts index 00d7e034..52baf985 100644 --- a/src/infrastructure/application/cli/cli.ts +++ b/src/infrastructure/application/cli/cli.ts @@ -48,7 +48,7 @@ import {Command, CommandInput} from '@/application/cli/command/command'; import {AdminCommand, AdminInput} from '@/application/cli/command/admin'; import {JsxWrapperCodemod} from '@/application/project/code/transformation/javascript/jsxWrapperCodemod'; import {JavaScriptCodemod} from '@/application/project/code/transformation/javascript/javaScriptCodemod'; -import {NextJsMiddlewareCodemod} from '@/application/project/code/transformation/javascript/nextJsMiddlewareCodemod'; +import {NextJsProxyCodemod} from '@/application/project/code/transformation/javascript/nextJsProxyCodemod'; import {CodeFormatter} from '@/application/project/code/formatting/formatter'; import {FormatCodemod} from '@/application/project/code/transformation/formatCodemod'; import {FileCodemod} from '@/application/project/code/transformation/fileCodemod'; @@ -1796,6 +1796,26 @@ export class Cli { }, }; + const createProxyCodemod = (proxyName: string): Codemod => new FormatCodemod( + formatter, + new FileCodemod({ + fileSystem: this.getFileSystem(), + codemod: new JavaScriptCodemod({ + languages: ['typescript', 'jsx'], + codemod: new NextJsProxyCodemod({ + // eslint-disable-next-line max-len -- Ignore for readability + matcherPattern: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', + exportName: proxyName, + import: { + module: `@croct/plug-next/${proxyName}`, + proxyName: proxyName, + proxyFactoryName: 'withCroct', + }, + }), + }), + }), + ); + return new PlugNextSdk({ ...config, plugins: [this.createStoryblokPlugin(Platform.NEXTJS)], @@ -1803,24 +1823,8 @@ export class Cli { applicationApi: this.getApplicationApi(), importResolver: importResolver, codemod: { - middleware: new FormatCodemod( - formatter, - new FileCodemod({ - fileSystem: this.getFileSystem(), - codemod: new JavaScriptCodemod({ - languages: ['typescript', 'jsx'], - codemod: new NextJsMiddlewareCodemod({ - // eslint-disable-next-line max-len -- Ignore for readability - matcherPattern: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', - import: { - module: '@croct/plug-next/middleware', - middlewareName: 'middleware', - middlewareFactoryName: 'withCroct', - }, - }), - }), - }), - ), + proxy: createProxyCodemod('proxy'), + middleware: createProxyCodemod('middleware'), appRouterProvider: new FormatCodemod( formatter, new FileCodemod({ @@ -2177,7 +2181,7 @@ export class Cli { return this.share( this.getJavaScriptFormatter, () => new JavaScriptFormatter({ - commandExecutor: this.getAsynchronousCommandExecutor(), + commandExecutor: this.getSynchronousCommandExecutor(), workingDirectory: this.workingDirectory, packageManager: this.getNodePackageManager(), fileSystem: this.getFileSystem(), diff --git a/src/infrastructure/application/project/javaScriptFormatter.ts b/src/infrastructure/application/project/javaScriptFormatter.ts index fc04ac5c..fd11316b 100644 --- a/src/infrastructure/application/project/javaScriptFormatter.ts +++ b/src/infrastructure/application/project/javaScriptFormatter.ts @@ -2,7 +2,7 @@ import {CodeFormatter, CodeFormatterError} from '@/application/project/code/form import {FileSystem} from '@/application/fs/fileSystem'; import {Dependency, PackageManager} from '@/application/project/packageManager/packageManager'; import {WorkingDirectory} from '@/application/fs/workingDirectory/workingDirectory'; -import {CommandExecutor} from '@/application/system/process/executor'; +import {SynchronousCommandExecutor} from '@/application/system/process/executor'; import {Command} from '@/application/system/process/command'; type FormatterTool = { @@ -15,7 +15,7 @@ export type Configuration = { workingDirectory: WorkingDirectory, fileSystem: FileSystem, packageManager: PackageManager, - commandExecutor: CommandExecutor, + commandExecutor: SynchronousCommandExecutor, timeout?: number, tools: FormatterTool[], }; @@ -41,17 +41,19 @@ export class JavaScriptFormatter implements CodeFormatter { } } - private async run(command: Command): Promise { + private run(command: Command): Promise { const {commandExecutor, workingDirectory, timeout} = this.configuration; - const execution = await commandExecutor.run(command, { + const execution = commandExecutor.runSync(command, { workingDirectory: workingDirectory.get(), timeout: timeout, }); - if (await execution.wait() !== 0) { + if (execution.exitCode !== 0) { throw new CodeFormatterError('Failed to format code.'); } + + return Promise.resolve(); } private async getCommand(files: string[]): Promise { diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunction.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunction.ts deleted file mode 100644 index 6d29ccfc..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunction.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default () => { - console.log('middleware'); -} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportClass.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportClass.ts deleted file mode 100644 index 6daca39a..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportClass.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default class Middleware { - // invalid -} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedHofCall.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedHofCall.ts deleted file mode 100644 index 7b27ad66..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedHofCall.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { withCroct as croctMiddleware } from "@croct/plug-next/middleware"; - -export default croctMiddleware(function () { - console.log('middleware'); -}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedMiddlewareCall.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedMiddlewareCall.ts deleted file mode 100644 index dc8ef92d..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingAliasedMiddlewareCall.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { middleware } from "@croct/plug-next/middleware"; - -export default middleware(function () { - console.log('middleware'); -}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingHofCall.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingHofCall.ts deleted file mode 100644 index f176723c..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingHofCall.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { withCroct } from "@croct/plug-next/middleware"; - -export default withCroct(function () { - console.log('middleware'); -}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingImport.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingImport.ts deleted file mode 100644 index 75a297ac..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingImport.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { withCroct } from "@croct/plug-next/middleware"; - -export function middleware() { - console.log('middleware'); -} - -export const config = { - matcher: '.*' -}; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingImportAliased.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingImportAliased.ts deleted file mode 100644 index f74fe09c..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingImportAliased.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { withCroct as croctMiddleware } from "@croct/plug-next/middleware"; - -export function middleware() { - console.log('middleware'); -} - -export const config = { - matcher: '.*' -}; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareCall.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareCall.ts deleted file mode 100644 index 7d54062f..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareCall.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { middleware as croctMiddleware } from "@croct/plug-next/middleware"; - -export default croctMiddleware(function () { - console.log('middleware'); -}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareReexport.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareReexport.ts deleted file mode 100644 index 1fd02b66..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingMiddlewareReexport.ts +++ /dev/null @@ -1 +0,0 @@ -export { middleware } from "@croct/plug-next/middleware"; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/matcherAlias.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/matcherAlias.ts deleted file mode 100644 index 76d3f540..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/matcherAlias.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function middleware() { - console.log('middleware'); -} - -export const config = { - matcher: '.*' -}; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunction.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunction.ts deleted file mode 100644 index ac53e59e..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunction.ts +++ /dev/null @@ -1 +0,0 @@ -export const middleware = () => console.log('middleware'); diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunctionWithBody.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunctionWithBody.ts deleted file mode 100644 index f1e23737..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportArrowFunctionWithBody.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const middleware = () => { - console.log('middleware'); -} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionDeclaration.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionDeclaration.ts deleted file mode 100644 index e05f5457..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionDeclaration.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function middleware() { - console.log('middleware'); -} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionExpression.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionExpression.ts deleted file mode 100644 index 25994bf0..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedExportFunctionExpression.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const middleware = function() { - console.log('middleware'); -} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedSpecifiedExport.ts b/test/application/project/code/transformation/fixtures/nextjs-middleware/namedSpecifiedExport.ts deleted file mode 100644 index 2312e638..00000000 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/namedSpecifiedExport.ts +++ /dev/null @@ -1,9 +0,0 @@ -const middleware = function() { - console.log('middleware'); -} - -const config = { - matcher: '.*' -}; - -export { middleware, config }; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterDefaultExport.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterDefaultExport.ts similarity index 75% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterDefaultExport.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterDefaultExport.ts index 1bb230b2..4c5c822b 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterDefaultExport.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterDefaultExport.ts @@ -1,6 +1,6 @@ import type { NextRequest } from 'next/server' -export function middleware(request: NextRequest): void { +export function proxy(request: NextRequest): void { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterNamedExport.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterNamedExport.ts similarity index 72% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterNamedExport.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterNamedExport.ts index 2a02b05e..b499be93 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterNamedExport.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterNamedExport.ts @@ -1,6 +1,6 @@ import type { NextRequest } from 'next/server' -export default function middleware(request: NextRequest): void { +export default function proxy(request: NextRequest): void { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterMiddlewareWithReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterProxyWithReference.ts similarity index 90% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterMiddlewareWithReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterProxyWithReference.ts index 6f6ab230..2963150d 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configAfterMiddlewareWithReference.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configAfterProxyWithReference.ts @@ -1,7 +1,7 @@ import type { NextRequest } from 'next/server' -// middleware -export function middleware(request: NextRequest): void { +// proxy +export function proxy(request: NextRequest): void { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configInvalidReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configInvalidReference.ts similarity index 62% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configInvalidReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configInvalidReference.ts index 1be4f0ed..41a75c18 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configInvalidReference.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configInvalidReference.ts @@ -1,6 +1,6 @@ import type { NextRequest } from 'next/server' -export default function middleware(request: NextRequest): void { +export default function proxy(request: NextRequest): void { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithArrayMatcher.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithArrayMatcher.ts similarity index 75% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configWithArrayMatcher.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configWithArrayMatcher.ts index a162f048..39eb437a 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithArrayMatcher.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithArrayMatcher.ts @@ -4,6 +4,6 @@ export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], } -export function middleware(request: NextRequest): void { +export function proxy(request: NextRequest): void { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithIndirectVariableReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithIndirectVariableReference.ts similarity index 85% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configWithIndirectVariableReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configWithIndirectVariableReference.ts index 9dbbe4c0..344aac83 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithIndirectVariableReference.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithIndirectVariableReference.ts @@ -8,6 +8,6 @@ const indirectReference = configValue; export const config = indirectReference; -export function middleware(request) { +export function proxy(request) { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithStringMatcher.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithStringMatcher.ts similarity index 76% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configWithStringMatcher.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configWithStringMatcher.ts index 1aa003f7..3f5dbd0d 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithStringMatcher.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithStringMatcher.ts @@ -2,6 +2,6 @@ export const config = { matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)', } -export function middleware(request) { +export function proxy(request) { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithVariableMatcher.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithVariableMatcher.ts similarity index 79% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configWithVariableMatcher.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configWithVariableMatcher.ts index f42008db..6cbfe24f 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithVariableMatcher.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithVariableMatcher.ts @@ -4,6 +4,6 @@ export const config = { matcher: regex, } -export function middleware(request) { +export function proxy(request) { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithVariableReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithVariableReference.ts similarity index 82% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configWithVariableReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configWithVariableReference.ts index 3fa545d7..c5ad37ce 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithVariableReference.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithVariableReference.ts @@ -6,6 +6,6 @@ const configValue = { export const config = configValue; -export function middleware(request) { +export function proxy(request) { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithoutMatcher.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithoutMatcher.ts similarity index 60% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/configWithoutMatcher.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/configWithoutMatcher.ts index 75cc3c30..9b9afbdf 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/configWithoutMatcher.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/configWithoutMatcher.ts @@ -1,6 +1,6 @@ export const config = { } -export function middleware(request) { +export function proxy(request) { console.log(request.url); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportAnonymousFunction.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportAnonymousFunction.ts similarity index 50% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportAnonymousFunction.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportAnonymousFunction.ts index 745cb850..e6a07f3b 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportAnonymousFunction.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportAnonymousFunction.ts @@ -1,3 +1,3 @@ export default function () { - console.log('middleware'); + console.log('proxy'); } diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunction.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunction.ts new file mode 100644 index 00000000..bfa2f607 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunction.ts @@ -0,0 +1,3 @@ +export default () => { + console.log('proxy'); +} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunctionReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunctionReference.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunctionReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunctionReference.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunctionWithBodyReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunctionWithBodyReference.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportArrowFunctionWithBodyReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportArrowFunctionWithBodyReference.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportClass.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportClass.ts new file mode 100644 index 00000000..a5db8ae2 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportClass.ts @@ -0,0 +1,3 @@ +export default class Proxy { + // invalid +} diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportFunctionDeclaration.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportFunctionDeclaration.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportFunctionDeclaration.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportFunctionDeclaration.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportFunctionDeclarationReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportFunctionDeclarationReference.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportFunctionDeclarationReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportFunctionDeclarationReference.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportFunctionExpressionReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportFunctionExpressionReference.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportFunctionExpressionReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportFunctionExpressionReference.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportIndirectReference.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportIndirectReference.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/defaultExportIndirectReference.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/defaultExportIndirectReference.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/empty.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/empty.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/empty.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/empty.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedHofCall.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedHofCall.ts new file mode 100644 index 00000000..502b1737 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedHofCall.ts @@ -0,0 +1,5 @@ +import { withCroct as croctProxy } from "@croct/plug-next/proxy"; + +export default croctProxy(function () { + console.log('proxy'); +}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedProxyCall.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedProxyCall.ts new file mode 100644 index 00000000..83100491 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingAliasedProxyCall.ts @@ -0,0 +1,5 @@ +import { proxy } from "@croct/plug-next/proxy"; + +export default proxy(function () { + console.log('proxy'); +}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingConfig.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingConfig.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/existingConfig.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/existingConfig.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingConfigArrayMatcher.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingConfigArrayMatcher.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/existingConfigArrayMatcher.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/existingConfigArrayMatcher.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/existingConfigMatcher.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingConfigMatcher.ts similarity index 100% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/existingConfigMatcher.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/existingConfigMatcher.ts diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingHofCall.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingHofCall.ts new file mode 100644 index 00000000..a9ccf314 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingHofCall.ts @@ -0,0 +1,5 @@ +import { withCroct } from "@croct/plug-next/proxy"; + +export default withCroct(function () { + console.log('proxy'); +}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingImport.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingImport.ts new file mode 100644 index 00000000..7e6d4f19 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingImport.ts @@ -0,0 +1,9 @@ +import { withCroct } from "@croct/plug-next/proxy"; + +export function proxy() { + console.log('proxy'); +} + +export const config = { + matcher: '.*' +}; diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingImportAliased.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingImportAliased.ts new file mode 100644 index 00000000..36f08927 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingImportAliased.ts @@ -0,0 +1,9 @@ +import { withCroct as croctProxy } from "@croct/plug-next/proxy"; + +export function proxy() { + console.log('proxy'); +} + +export const config = { + matcher: '.*' +}; diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyCall.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyCall.ts new file mode 100644 index 00000000..ce508472 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyCall.ts @@ -0,0 +1,5 @@ +import { proxy as croctProxy } from "@croct/plug-next/proxy"; + +export default croctProxy(function () { + console.log('proxy'); +}); diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyReexport.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyReexport.ts new file mode 100644 index 00000000..a7c089d3 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/existingProxyReexport.ts @@ -0,0 +1 @@ +export { proxy } from "@croct/plug-next/proxy"; diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/matcherAlias.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/matcherAlias.ts new file mode 100644 index 00000000..eba327e4 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/matcherAlias.ts @@ -0,0 +1,7 @@ +export function proxy() { + console.log('proxy'); +} + +export const config = { + matcher: '.*' +}; diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunction.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunction.ts new file mode 100644 index 00000000..bc9a6e78 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunction.ts @@ -0,0 +1 @@ +export const proxy = () => console.log('proxy'); diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunctionWithBody.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunctionWithBody.ts new file mode 100644 index 00000000..3e6d1ae5 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportArrowFunctionWithBody.ts @@ -0,0 +1,3 @@ +export const proxy = () => { + console.log('proxy'); +} diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionDeclaration.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionDeclaration.ts new file mode 100644 index 00000000..45a16915 --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionDeclaration.ts @@ -0,0 +1,3 @@ +export function proxy() { + console.log('proxy'); +} diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionExpression.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionExpression.ts new file mode 100644 index 00000000..488edfdd --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedExportFunctionExpression.ts @@ -0,0 +1,3 @@ +export const proxy = function() { + console.log('proxy'); +} diff --git a/test/application/project/code/transformation/fixtures/nextjs-proxy/namedSpecifiedExport.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedSpecifiedExport.ts new file mode 100644 index 00000000..9294b98c --- /dev/null +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/namedSpecifiedExport.ts @@ -0,0 +1,9 @@ +const proxy = function() { + console.log('proxy'); +} + +const config = { + matcher: '.*' +}; + +export { proxy, config }; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/specifiedExportWithAliases.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/specifiedExportWithAliases.ts similarity index 51% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/specifiedExportWithAliases.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/specifiedExportWithAliases.ts index cb954de7..095d9a74 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/specifiedExportWithAliases.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/specifiedExportWithAliases.ts @@ -1,8 +1,8 @@ function unrelated() { } -const _middlewareFn = function() { - console.log('middleware'); +const _proxyFn = function() { + console.log('proxy'); } const _config = { @@ -10,6 +10,6 @@ const _config = { }; export { - _middlewareFn as default, + _proxyFn as default, _config as config }; diff --git a/test/application/project/code/transformation/fixtures/nextjs-middleware/unrelatedExports.ts b/test/application/project/code/transformation/fixtures/nextjs-proxy/unrelatedExports.ts similarity index 64% rename from test/application/project/code/transformation/fixtures/nextjs-middleware/unrelatedExports.ts rename to test/application/project/code/transformation/fixtures/nextjs-proxy/unrelatedExports.ts index 24361094..b9794271 100644 --- a/test/application/project/code/transformation/fixtures/nextjs-middleware/unrelatedExports.ts +++ b/test/application/project/code/transformation/fixtures/nextjs-proxy/unrelatedExports.ts @@ -5,5 +5,5 @@ export default () => { } -export function middleware() { +export function proxy() { } diff --git a/test/application/project/code/transformation/javascript/__snapshots__/nextJsMiddlewareCodemod.test.ts.snap b/test/application/project/code/transformation/javascript/__snapshots__/nextJsMiddlewareCodemod.test.ts.snap deleted file mode 100644 index 958f392d..00000000 --- a/test/application/project/code/transformation/javascript/__snapshots__/nextJsMiddlewareCodemod.test.ts.snap +++ /dev/null @@ -1,577 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NextJsMiddlewareCodemod should correctly transform configAfterDefaultExport.ts: configAfterDefaultExport.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -import type { NextRequest } from 'next/server' - -export const config = { - matcher: [ - '/((?!api|_next/static|_next/image|favicon.ico).*)', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export const middleware = withCroct({ - matcher: config.matcher, - - next: function(request: NextRequest) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configAfterMiddlewareWithReference.ts: configAfterMiddlewareWithReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -import type { NextRequest } from 'next/server' - -// pattern -const PATTERN = '/((?!api|_next/static|_next/image|favicon.ico).*)'; - -// matcher -class Matcher { - static readonly PATTERN = PATTERN; -} - -// getMatcher -function getMatcher() { - return Matcher.PATTERN; -} - -// currentMatcher -const currentMatcher = getMatcher(); - -// currentConfig -const currentConfig = { - matcher: [ - ...(Array.isArray(currentMatcher) ? currentMatcher : [currentMatcher]), - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export const middleware = withCroct({ - matcher: currentConfig.matcher, - - next: function(request: NextRequest) { - console.log(request.url); - } -}); - -function unrelated() { -} - -// Non-global scope -{ - // Different scope, should not be considered - const PATTERN = null; - - function getMatcher() { - } - - class Matcher { - } -} - -// config -export const config = currentConfig; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configAfterNamedExport.ts: configAfterNamedExport.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -import type { NextRequest } from 'next/server' - -export const config = { - matcher: [ - '/((?!api|_next/static|_next/image|favicon.ico).*)', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export default withCroct({ - matcher: config.matcher, - - next: function middleware(request: NextRequest) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configInvalidReference.ts: configInvalidReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -import type { NextRequest } from 'next/server' - -export default withCroct(function middleware(request: NextRequest) { - console.log(request.url); -}); - -export const config = bar; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configWithArrayMatcher.ts: configWithArrayMatcher.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -import type { NextRequest } from 'next/server' - -export const config = { - matcher: [ - '/((?!api|_next/static|_next/image|favicon.ico).*)', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export const middleware = withCroct({ - matcher: config.matcher, - - next: function(request: NextRequest) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configWithIndirectVariableReference.ts: configWithIndirectVariableReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -const regex = '/((?!api|_next/static|_next/image|favicon.ico).*)'; - -const configValue = { - matcher: [ - ...(Array.isArray(regex) ? regex : [regex]), - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -const indirectReference = configValue; -export const config = indirectReference; - -export const middleware = withCroct({ - matcher: configValue.matcher, - - next: function(request) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configWithStringMatcher.ts: configWithStringMatcher.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { - matcher: [ - '/((?!api|_next/static|_next/image|favicon.ico).*)', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export const middleware = withCroct({ - matcher: config.matcher, - - next: function(request) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configWithVariableMatcher.ts: configWithVariableMatcher.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -const regex = '/((?!api|_next/static|_next/image|favicon.ico).*)'; - -export const config = { - matcher: [ - ...(Array.isArray(regex) ? regex : [regex]), - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export const middleware = withCroct({ - matcher: config.matcher, - - next: function(request) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configWithVariableReference.ts: configWithVariableReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -const regex = '/((?!api|_next/static|_next/image|favicon.ico).*)'; - -const configValue = { - matcher: [ - ...(Array.isArray(regex) ? regex : [regex]), - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export const config = configValue; - -export const middleware = withCroct({ - matcher: configValue.matcher, - - next: function(request) { - console.log(request.url); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform configWithoutMatcher.ts: configWithoutMatcher.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { -} - -export const middleware = withCroct(function(request) { - console.log(request.url); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportAnonymousFunction.ts: defaultExportAnonymousFunction.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export default withCroct(function() { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportArrowFunction.ts: defaultExportArrowFunction.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export default withCroct(() => { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportArrowFunctionReference.ts: defaultExportArrowFunctionReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -const anything = withCroct((request) => console.log(request.url)); -export default anything; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportArrowFunctionWithBodyReference.ts: defaultExportArrowFunctionWithBodyReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -const anything = withCroct((request) => { - console.log(request.url); -}) - -export default anything; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportClass.ts: defaultExportClass.ts 1`] = ` -"export default class Middleware { - // invalid -} - -export { middleware } from "@croct/plug-next/middleware"; - -export const config = { - matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" -}; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportFunctionDeclaration.ts: defaultExportFunctionDeclaration.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export default withCroct(function anything(request) { - console.log(request.url); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportFunctionDeclarationReference.ts: defaultExportFunctionDeclarationReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -const anything = withCroct(function(request) { - console.log(request.url); -}); - -export default anything; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportFunctionExpressionReference.ts: defaultExportFunctionExpressionReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -const anything = withCroct(function (request) { - console.log(request.url); -}) - -export default anything; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform defaultExportIndirectReference.ts: defaultExportIndirectReference.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -const anything = function (request) { - console.log(request.url); -} - -const something = withCroct(anything); -export default something; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform empty.ts: empty.ts 1`] = ` -"export { middleware } from "@croct/plug-next/middleware"; - -export const config = { - matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" -};" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingAliasedHofCall.ts: existingAliasedHofCall.ts 1`] = ` -"import { withCroct as croctMiddleware } from "@croct/plug-next/middleware"; - -export default croctMiddleware(function () { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingAliasedMiddlewareCall.ts: existingAliasedMiddlewareCall.ts 1`] = ` -"import { middleware } from "@croct/plug-next/middleware"; - -export default middleware(function () { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingConfig.ts: existingConfig.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { - matcher: [ - '/((?!api|_next/static|_next/image|favicon.ico).*)', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ], -} - -export default withCroct({ - matcher: config.matcher -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingConfigArrayMatcher.ts: existingConfigArrayMatcher.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { - matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'], -} - -export default withCroct({ - matcher: config.matcher -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingConfigMatcher.ts: existingConfigMatcher.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { - matcher: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', -} - -export default withCroct({ - matcher: config.matcher -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingHofCall.ts: existingHofCall.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export default withCroct(function () { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingImport.ts: existingImport.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { - matcher: [ - '.*', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ] -}; - -export const middleware = withCroct({ - matcher: config.matcher, - - next: function() { - console.log('middleware'); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingImportAliased.ts: existingImportAliased.ts 1`] = ` -"import { withCroct as croctMiddleware } from "@croct/plug-next/middleware"; - -export const config = { - matcher: [ - '.*', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ] -}; - -export const middleware = croctMiddleware({ - matcher: config.matcher, - - next: function() { - console.log('middleware'); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingMiddlewareCall.ts: existingMiddlewareCall.ts 1`] = ` -"import { middleware as croctMiddleware } from "@croct/plug-next/middleware"; - -export default croctMiddleware(function () { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform existingMiddlewareReexport.ts: existingMiddlewareReexport.ts 1`] = ` -"export { middleware } from "@croct/plug-next/middleware"; - -export const config = { - matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" -}; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform matcherAlias.ts: matcherAlias.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const config = { - matcher: [ - '.*', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ] -}; - -export const middleware = withCroct({ - matcher: config.matcher, - - next: function() { - console.log('middleware'); - } -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform namedExportArrowFunction.ts: namedExportArrowFunction.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; -export const middleware = withCroct(() => console.log('middleware')); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform namedExportArrowFunctionWithBody.ts: namedExportArrowFunctionWithBody.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const middleware = withCroct(() => { - console.log('middleware'); -}) -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform namedExportFunctionDeclaration.ts: namedExportFunctionDeclaration.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const middleware = withCroct(function() { - console.log('middleware'); -}); -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform namedExportFunctionExpression.ts: namedExportFunctionExpression.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export const middleware = withCroct(function() { - console.log('middleware'); -}) -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform namedSpecifiedExport.ts: namedSpecifiedExport.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -const config = { - matcher: [ - '.*', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ] -}; - -const middleware = withCroct({ - matcher: config.matcher, - - next: function() { - console.log('middleware'); - } -}) - -export { middleware, config }; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform specifiedExportWithAliases.ts: specifiedExportWithAliases.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -function unrelated() { -} - -const _config = { - matcher: [ - '.*', - "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" - ] -}; - -const _middlewareFn = withCroct({ - matcher: _config.matcher, - - next: function() { - console.log('middleware'); - } -}) - -export { - _middlewareFn as default, - _config as config -}; -" -`; - -exports[`NextJsMiddlewareCodemod should correctly transform unrelatedExports.ts: unrelatedExports.ts 1`] = ` -"import { withCroct } from "@croct/plug-next/middleware"; - -export function foo() { -} - -export default withCroct(() => { - -}); - -export function middleware() { -} -" -`; diff --git a/test/application/project/code/transformation/javascript/nextJsMiddlewareCodemod.test.ts b/test/application/project/code/transformation/javascript/nextJsProxyCodemod.test.ts similarity index 64% rename from test/application/project/code/transformation/javascript/nextJsMiddlewareCodemod.test.ts rename to test/application/project/code/transformation/javascript/nextJsProxyCodemod.test.ts index 4a5fe1dc..f8e185bd 100644 --- a/test/application/project/code/transformation/javascript/nextJsMiddlewareCodemod.test.ts +++ b/test/application/project/code/transformation/javascript/nextJsProxyCodemod.test.ts @@ -1,23 +1,24 @@ import {resolve} from 'path'; import { - MiddlewareConfiguration, - NextJsMiddlewareCodemod, -} from '@/application/project/code/transformation/javascript/nextJsMiddlewareCodemod'; + ProxyConfiguration, + NextJsProxyCodemod, +} from '@/application/project/code/transformation/javascript/nextJsProxyCodemod'; import {loadFixtures} from '../fixtures'; import {JavaScriptCodemod} from '@/application/project/code/transformation/javascript/javaScriptCodemod'; -describe('NextJsMiddlewareCodemod', () => { - const defaultOptions: MiddlewareConfiguration = { +describe('NextJsProxyCodemod', () => { + const defaultOptions: ProxyConfiguration = { matcherPattern: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', + exportName: 'proxy', import: { - module: '@croct/plug-next/middleware', - middlewareFactoryName: 'withCroct', - middlewareName: 'middleware', + module: '@croct/plug-next/proxy', + proxyFactoryName: 'withCroct', + proxyName: 'proxy', }, }; - const scenarios = loadFixtures( - resolve(__dirname, '../fixtures/nextjs-middleware'), + const scenarios = loadFixtures( + resolve(__dirname, '../fixtures/nextjs-proxy'), defaultOptions, { 'matcherAlias.ts': { @@ -31,7 +32,7 @@ describe('NextJsMiddlewareCodemod', () => { it.each(scenarios)('should correctly transform $name', async ({name, fixture, options}) => { const transformer = new JavaScriptCodemod({ languages: ['typescript'], - codemod: new NextJsMiddlewareCodemod(options), + codemod: new NextJsProxyCodemod(options), }); const output = await transformer.apply(fixture); From db58e05b2e61114450aa760ff7e6916fa5227249 Mon Sep 17 00:00:00 2001 From: Marcos Passos Date: Tue, 27 Jan 2026 15:07:00 -0300 Subject: [PATCH 7/7] Update snapshot --- .../nextJsProxyCodemod.test.ts.snap | 577 ++++++++++++++++++ 1 file changed, 577 insertions(+) create mode 100644 test/application/project/code/transformation/javascript/__snapshots__/nextJsProxyCodemod.test.ts.snap diff --git a/test/application/project/code/transformation/javascript/__snapshots__/nextJsProxyCodemod.test.ts.snap b/test/application/project/code/transformation/javascript/__snapshots__/nextJsProxyCodemod.test.ts.snap new file mode 100644 index 00000000..ccb35262 --- /dev/null +++ b/test/application/project/code/transformation/javascript/__snapshots__/nextJsProxyCodemod.test.ts.snap @@ -0,0 +1,577 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NextJsProxyCodemod should correctly transform configAfterDefaultExport.ts: configAfterDefaultExport.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +import type { NextRequest } from 'next/server' + +export const config = { + matcher: [ + '/((?!api|_next/static|_next/image|favicon.ico).*)', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export const proxy = withCroct({ + matcher: config.matcher, + + next: function(request: NextRequest) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configAfterNamedExport.ts: configAfterNamedExport.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +import type { NextRequest } from 'next/server' + +export const config = { + matcher: [ + '/((?!api|_next/static|_next/image|favicon.ico).*)', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export default withCroct({ + matcher: config.matcher, + + next: function proxy(request: NextRequest) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configAfterProxyWithReference.ts: configAfterProxyWithReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +import type { NextRequest } from 'next/server' + +// pattern +const PATTERN = '/((?!api|_next/static|_next/image|favicon.ico).*)'; + +// matcher +class Matcher { + static readonly PATTERN = PATTERN; +} + +// getMatcher +function getMatcher() { + return Matcher.PATTERN; +} + +// currentMatcher +const currentMatcher = getMatcher(); + +// currentConfig +const currentConfig = { + matcher: [ + ...(Array.isArray(currentMatcher) ? currentMatcher : [currentMatcher]), + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export const proxy = withCroct({ + matcher: currentConfig.matcher, + + next: function(request: NextRequest) { + console.log(request.url); + } +}); + +function unrelated() { +} + +// Non-global scope +{ + // Different scope, should not be considered + const PATTERN = null; + + function getMatcher() { + } + + class Matcher { + } +} + +// config +export const config = currentConfig; +" +`; + +exports[`NextJsProxyCodemod should correctly transform configInvalidReference.ts: configInvalidReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +import type { NextRequest } from 'next/server' + +export default withCroct(function proxy(request: NextRequest) { + console.log(request.url); +}); + +export const config = bar; +" +`; + +exports[`NextJsProxyCodemod should correctly transform configWithArrayMatcher.ts: configWithArrayMatcher.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +import type { NextRequest } from 'next/server' + +export const config = { + matcher: [ + '/((?!api|_next/static|_next/image|favicon.ico).*)', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export const proxy = withCroct({ + matcher: config.matcher, + + next: function(request: NextRequest) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configWithIndirectVariableReference.ts: configWithIndirectVariableReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +const regex = '/((?!api|_next/static|_next/image|favicon.ico).*)'; + +const configValue = { + matcher: [ + ...(Array.isArray(regex) ? regex : [regex]), + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +const indirectReference = configValue; +export const config = indirectReference; + +export const proxy = withCroct({ + matcher: configValue.matcher, + + next: function(request) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configWithStringMatcher.ts: configWithStringMatcher.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { + matcher: [ + '/((?!api|_next/static|_next/image|favicon.ico).*)', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export const proxy = withCroct({ + matcher: config.matcher, + + next: function(request) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configWithVariableMatcher.ts: configWithVariableMatcher.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +const regex = '/((?!api|_next/static|_next/image|favicon.ico).*)'; + +export const config = { + matcher: [ + ...(Array.isArray(regex) ? regex : [regex]), + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export const proxy = withCroct({ + matcher: config.matcher, + + next: function(request) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configWithVariableReference.ts: configWithVariableReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +const regex = '/((?!api|_next/static|_next/image|favicon.ico).*)'; + +const configValue = { + matcher: [ + ...(Array.isArray(regex) ? regex : [regex]), + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export const config = configValue; + +export const proxy = withCroct({ + matcher: configValue.matcher, + + next: function(request) { + console.log(request.url); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform configWithoutMatcher.ts: configWithoutMatcher.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { +} + +export const proxy = withCroct(function(request) { + console.log(request.url); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportAnonymousFunction.ts: defaultExportAnonymousFunction.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export default withCroct(function() { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportArrowFunction.ts: defaultExportArrowFunction.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export default withCroct(() => { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportArrowFunctionReference.ts: defaultExportArrowFunctionReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +const anything = withCroct((request) => console.log(request.url)); +export default anything; +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportArrowFunctionWithBodyReference.ts: defaultExportArrowFunctionWithBodyReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +const anything = withCroct((request) => { + console.log(request.url); +}) + +export default anything; +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportClass.ts: defaultExportClass.ts 1`] = ` +"export default class Proxy { + // invalid +} + +export { proxy } from "@croct/plug-next/proxy"; + +export const config = { + matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" +}; +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportFunctionDeclaration.ts: defaultExportFunctionDeclaration.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export default withCroct(function anything(request) { + console.log(request.url); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportFunctionDeclarationReference.ts: defaultExportFunctionDeclarationReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +const anything = withCroct(function(request) { + console.log(request.url); +}); + +export default anything; +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportFunctionExpressionReference.ts: defaultExportFunctionExpressionReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +const anything = withCroct(function (request) { + console.log(request.url); +}) + +export default anything; +" +`; + +exports[`NextJsProxyCodemod should correctly transform defaultExportIndirectReference.ts: defaultExportIndirectReference.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +const anything = function (request) { + console.log(request.url); +} + +const something = withCroct(anything); +export default something; +" +`; + +exports[`NextJsProxyCodemod should correctly transform empty.ts: empty.ts 1`] = ` +"export { proxy } from "@croct/plug-next/proxy"; + +export const config = { + matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" +};" +`; + +exports[`NextJsProxyCodemod should correctly transform existingAliasedHofCall.ts: existingAliasedHofCall.ts 1`] = ` +"import { withCroct as croctProxy } from "@croct/plug-next/proxy"; + +export default croctProxy(function () { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingAliasedProxyCall.ts: existingAliasedProxyCall.ts 1`] = ` +"import { proxy } from "@croct/plug-next/proxy"; + +export default proxy(function () { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingConfig.ts: existingConfig.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { + matcher: [ + '/((?!api|_next/static|_next/image|favicon.ico).*)', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ], +} + +export default withCroct({ + matcher: config.matcher +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingConfigArrayMatcher.ts: existingConfigArrayMatcher.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { + matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'], +} + +export default withCroct({ + matcher: config.matcher +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingConfigMatcher.ts: existingConfigMatcher.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { + matcher: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', +} + +export default withCroct({ + matcher: config.matcher +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingHofCall.ts: existingHofCall.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export default withCroct(function () { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingImport.ts: existingImport.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { + matcher: [ + '.*', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ] +}; + +export const proxy = withCroct({ + matcher: config.matcher, + + next: function() { + console.log('proxy'); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingImportAliased.ts: existingImportAliased.ts 1`] = ` +"import { withCroct as croctProxy } from "@croct/plug-next/proxy"; + +export const config = { + matcher: [ + '.*', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ] +}; + +export const proxy = croctProxy({ + matcher: config.matcher, + + next: function() { + console.log('proxy'); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingProxyCall.ts: existingProxyCall.ts 1`] = ` +"import { proxy as croctProxy } from "@croct/plug-next/proxy"; + +export default croctProxy(function () { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform existingProxyReexport.ts: existingProxyReexport.ts 1`] = ` +"export { proxy } from "@croct/plug-next/proxy"; + +export const config = { + matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" +}; +" +`; + +exports[`NextJsProxyCodemod should correctly transform matcherAlias.ts: matcherAlias.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const config = { + matcher: [ + '.*', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ] +}; + +export const proxy = withCroct({ + matcher: config.matcher, + + next: function() { + console.log('proxy'); + } +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform namedExportArrowFunction.ts: namedExportArrowFunction.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; +export const proxy = withCroct(() => console.log('proxy')); +" +`; + +exports[`NextJsProxyCodemod should correctly transform namedExportArrowFunctionWithBody.ts: namedExportArrowFunctionWithBody.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const proxy = withCroct(() => { + console.log('proxy'); +}) +" +`; + +exports[`NextJsProxyCodemod should correctly transform namedExportFunctionDeclaration.ts: namedExportFunctionDeclaration.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const proxy = withCroct(function() { + console.log('proxy'); +}); +" +`; + +exports[`NextJsProxyCodemod should correctly transform namedExportFunctionExpression.ts: namedExportFunctionExpression.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export const proxy = withCroct(function() { + console.log('proxy'); +}) +" +`; + +exports[`NextJsProxyCodemod should correctly transform namedSpecifiedExport.ts: namedSpecifiedExport.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +const config = { + matcher: [ + '.*', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ] +}; + +const proxy = withCroct({ + matcher: config.matcher, + + next: function() { + console.log('proxy'); + } +}) + +export { proxy, config }; +" +`; + +exports[`NextJsProxyCodemod should correctly transform specifiedExportWithAliases.ts: specifiedExportWithAliases.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +function unrelated() { +} + +const _config = { + matcher: [ + '.*', + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)" + ] +}; + +const _proxyFn = withCroct({ + matcher: _config.matcher, + + next: function() { + console.log('proxy'); + } +}) + +export { + _proxyFn as default, + _config as config +}; +" +`; + +exports[`NextJsProxyCodemod should correctly transform unrelatedExports.ts: unrelatedExports.ts 1`] = ` +"import { withCroct } from "@croct/plug-next/proxy"; + +export function foo() { +} + +export default withCroct(() => { + +}); + +export function proxy() { +} +" +`;