From 6946752a7c292b732a18332e0d525ed6757b0e90 Mon Sep 17 00:00:00 2001 From: Matthew McClure Date: Tue, 29 Aug 2023 14:25:14 -0700 Subject: [PATCH] try our hand at editing some next configs --- package-lock.json | 311 +++++++++++++++++- package.json | 2 + src/cli/init.ts | 20 ++ src/cli/lib/next-config.ts | 85 +++++ src/constants.ts | 6 +- src/with-next-video.ts | 8 + tests/cli/lib/json-configs.test.ts | 6 +- tests/cli/lib/next-config.test.ts | 55 ++++ tests/factories/next.config.js | 8 + tests/factories/next.config.mjs | 8 + tests/factories/next.function.config.js | 9 + tests/factories/next.promise.config.js | 9 + tests/{cli => }/factories/package.dep.json | 0 tests/{cli => }/factories/package.devDep.json | 0 tests/{cli => }/factories/package.none.json | 0 tests/with-next-video.test.ts | 50 +++ 16 files changed, 572 insertions(+), 5 deletions(-) create mode 100644 src/cli/lib/next-config.ts create mode 100644 tests/cli/lib/next-config.test.ts create mode 100644 tests/factories/next.config.js create mode 100644 tests/factories/next.config.mjs create mode 100644 tests/factories/next.function.config.js create mode 100644 tests/factories/next.promise.config.js rename tests/{cli => }/factories/package.dep.json (100%) rename tests/{cli => }/factories/package.devDep.json (100%) rename tests/{cli => }/factories/package.none.json (100%) create mode 100644 tests/with-next-video.test.ts diff --git a/package-lock.json b/package-lock.json index ef1d7ce..9848227 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@mux/mux-player-react": "1.12.1-canary.0-e90e096", "chalk": "^4.1.2", "chokidar": "^3.5.3", + "magicast": "^0.2.10", "sharp": "^0.32.5", "symlink-dir": "^5.1.1", "thumbhash": "^0.1.1", @@ -48,6 +49,46 @@ } } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz", + "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild-kit/cjs-loader": { "version": "2.4.2", "dev": true, @@ -583,10 +624,43 @@ "node": ">= 8" } }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -925,6 +999,21 @@ "node": ">=4.0.0" } }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "license": "MIT", @@ -1001,6 +1090,11 @@ "once": "^1.4.0" } }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" + }, "node_modules/esbuild": { "version": "0.17.19", "dev": true, @@ -1051,6 +1145,18 @@ "node": ">=0.8.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "license": "MIT", @@ -1106,6 +1212,14 @@ "node": ">=8" } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -1255,6 +1369,17 @@ "node": ">= 6" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1278,6 +1403,17 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-proto": { "version": "1.0.1", "license": "MIT", @@ -1298,6 +1434,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hls.js": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.10.tgz", @@ -1358,6 +1508,21 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -1377,6 +1542,17 @@ "version": "1.1.6", "license": "MIT" }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "license": "MIT", @@ -1391,6 +1567,20 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "license": "MIT", @@ -1401,6 +1591,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "license": "MIT", @@ -1408,6 +1613,20 @@ "node": ">=0.12.0" } }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -1510,6 +1729,16 @@ "node": ">=10" } }, + "node_modules/magicast": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.2.10.tgz", + "integrity": "sha512-Ah2qatigknxwmoYCd9hx/mmVyrRNhDKiaWZIuW4gL6dWrAGMoOpCVkQ3VpGWARtkaJVFhe8uIphcsxDzLPQUyg==", + "dependencies": { + "@babel/parser": "^7.22.7", + "@babel/types": "^7.22.5", + "recast": "^0.23.3" + } + }, "node_modules/md5": { "version": "2.3.0", "license": "BSD-3-Clause", @@ -1687,6 +1916,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1878,6 +2130,21 @@ "node": ">=8.10.0" } }, + "node_modules/recast": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.4.tgz", + "integrity": "sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==", + "dependencies": { + "assert": "^2.0.0", + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/rename-overwrite": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/rename-overwrite/-/rename-overwrite-4.0.3.tgz", @@ -2148,7 +2415,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2311,6 +2577,14 @@ "node": ">=0.6.0" } }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -2325,6 +2599,11 @@ "version": "0.0.3", "license": "MIT" }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/tsx": { "version": "3.12.7", "dev": true, @@ -2392,6 +2671,18 @@ "node": ">= 10.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2431,6 +2722,24 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "license": "MIT", diff --git a/package.json b/package.json index e5af1ad..c118bee 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "build:esm": "tsc --module es2020", "build:cjs": "tsc --module commonjs --outDir dist/cjs", "postbuild:cjs": "echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", + "prepublish": "npm run build", "cli": "node --loader tsx --no-warnings ./src/cli", "test": "glob -c \"node --loader tsx --no-warnings --test\" \"./tests/**/*.test.ts\"" }, @@ -64,6 +65,7 @@ "@mux/mux-player-react": "1.12.1-canary.0-e90e096", "chalk": "^4.1.2", "chokidar": "^3.5.3", + "magicast": "^0.2.10", "sharp": "^0.32.5", "symlink-dir": "^5.1.1", "thumbhash": "^0.1.1", diff --git a/src/cli/init.ts b/src/cli/init.ts index 7f9aca8..1677ad2 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -8,6 +8,7 @@ import path from 'node:path'; import log, { Logger } from '../logger.js'; import { checkPackageJsonForNextVideo, updateTSConfigFileContent } from './lib/json-configs.js'; +import updateNextConfigFile from './lib/next-config.js'; const GITIGNORE_CONTENTS = `* !*.json @@ -167,6 +168,25 @@ export async function handler(argv: Arguments) { } } + try { + const update = await updateNextConfigFile(); + + if (update) { + changes.push([log.add, `Updated ${update.configPath} to include next-video.`]); + } + } catch (e: any) { + if (e.error === 'not_found') { + changes.push([ + log.error, + 'No next.config.js or next.config.mjs file found. Please add next-video to your config manually.', + ]); + } else if (e.error === 'already_added') { + changes.push([log.info, 'It seems like next-video is already added to your Next Config']); + } else { + changes.push([log.error, 'Failed to update next.config.js, please add next-video to your config manually.']); + } + } + log.success(`${chalk.magenta.bold('next-video')} initialized!`); changes.forEach(([loggerFn, change]) => loggerFn(change)); } diff --git a/src/cli/lib/next-config.ts b/src/cli/lib/next-config.ts new file mode 100644 index 0000000..168086d --- /dev/null +++ b/src/cli/lib/next-config.ts @@ -0,0 +1,85 @@ +import { builders, loadFile, generateCode, writeFile } from 'magicast'; + +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import { PACKAGE_NAME } from '../../constants.js'; + +const COMMON_TEMPLATE = ` + +// Everything below here added by the ${PACKAGE_NAME} CLI wizard. +// You should probably clean this up. + +const originalConfig = module.exports; + +const withNextVideo = require('${path.join(PACKAGE_NAME, 'process')}'); + +module.exports = withNextVideo(originalConfig); +`; + +function extensionToType(filePath: string) { + if (filePath.endsWith('.mjs')) { + return 'module'; + } + + return 'commonjs'; +} + +export default async function updateNextConfigFile(parentDir: string = './') { + let type: 'module' | 'commonjs' = 'commonjs'; + let configPath: string | undefined = undefined; + let configContents: string = ''; + + const pathsToCheck = ['next.config.js', 'next.config.mjs']; + + for (let i = 0; i < pathsToCheck.length; i++) { + console.log('asdf'); + const filePath = path.join(parentDir, pathsToCheck[i]); + let exists; + try { + exists = await fs.stat(filePath); + } catch (e) { + exists = false; + } + + if (exists) { + type = extensionToType(pathsToCheck[i]); + configPath = filePath; + configContents = await fs.readFile(filePath, 'utf-8'); + + break; + } + } + + if (!configPath) { + throw { error: 'not_found' }; + } + + if (configContents.includes(PACKAGE_NAME)) { + throw { error: 'already_added' }; + } + + if (type === 'commonjs') { + await fs.appendFile(configPath, COMMON_TEMPLATE); + + return { type, configPath }; + } + + if (type === 'module') { + const mod = await loadFile(configPath); + + mod.imports.$add({ + from: PACKAGE_NAME, + imported: 'withNextVideo', + local: 'withNextVideo', + }); + + const expressionToWrap = generateCode(mod.exports.default.$ast).code; + mod.exports.default = builders.raw(`withNextVideo(${expressionToWrap})`); + + // @ts-ignore + writeFile(mod, configPath); + + return { type, configPath }; + } +} diff --git a/src/constants.ts b/src/constants.ts index 766aae0..2782381 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,8 @@ import path from 'node:path'; +import fs from 'node:fs/promises'; + +const packageJsonContents = await fs.readFile(path.join(process.cwd(), 'package.json'), 'utf-8'); +const packageJson = JSON.parse(packageJsonContents); export const VIDEOS_PATH = path.join(process.cwd(), '/videos'); -export const PACKAGE_NAME = '@mux/next-video'; +export const PACKAGE_NAME = packageJson.name; diff --git a/src/with-next-video.ts b/src/with-next-video.ts index de407ad..885efd7 100644 --- a/src/with-next-video.ts +++ b/src/with-next-video.ts @@ -6,6 +6,7 @@ import fs from 'node:fs/promises'; import { VIDEOS_PATH } from './constants.js'; export default async function withNextVideo(nextConfig: any) { + // We should probably switch to using `phase` here, just a bit concerned about backwards compatibility. if (process.argv[2] === 'dev') { const TMP_PUBLIC_VIDEOS_PATH = path.join(process.cwd(), 'public/_videos'); @@ -16,6 +17,13 @@ export default async function withNextVideo(nextConfig: any) { }); } + if (typeof nextConfig === 'function') { + return async (...args: any[]) => { + const nextConfigResult = await Promise.resolve(nextConfig(...args)); + return withNextVideo(nextConfigResult); + }; + } + return Object.assign({}, nextConfig, { webpack(config: any, options: any) { if (!options.defaultLoaders) { diff --git a/tests/cli/lib/json-configs.test.ts b/tests/cli/lib/json-configs.test.ts index fcd604e..0cbbb1c 100644 --- a/tests/cli/lib/json-configs.test.ts +++ b/tests/cli/lib/json-configs.test.ts @@ -55,15 +55,15 @@ describe('json-configs', () => { describe('checkPackageJsonForNextVideo', () => { it('should return true if next-video is in devDependencies', async () => { - assert.equal(await checkPackageJsonForNextVideo('./tests/cli/factories/package.devDep.json'), true); + assert.equal(await checkPackageJsonForNextVideo('./tests/factories/package.devDep.json'), true); }); it('should return true if next-video is in dependencies', async () => { - assert.equal(await checkPackageJsonForNextVideo('./tests/cli/factories/package.dep.json'), true); + assert.equal(await checkPackageJsonForNextVideo('./tests/factories/package.dep.json'), true); }); it('should return false if next-video is in neither', async () => { - assert.equal(await checkPackageJsonForNextVideo('./tests/cli/factories/package.none.json'), false); + assert.equal(await checkPackageJsonForNextVideo('./tests/factories/package.none.json'), false); }); }); }); diff --git a/tests/cli/lib/next-config.test.ts b/tests/cli/lib/next-config.test.ts new file mode 100644 index 0000000..1e863cd --- /dev/null +++ b/tests/cli/lib/next-config.test.ts @@ -0,0 +1,55 @@ +import assert from 'node:assert'; +import { after, describe, it } from 'node:test'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import updateNextConfigFile from '../../../src/cli/lib/next-config.js'; + +function outputConfigName(configName: string) { + if (configName.endsWith('.mjs')) { + return 'next.config.mjs'; + } + + // We have to return cjs files so we can import them async in a test. + return 'next.config.js'; +} + +describe('updateNextConfig', () => { + let tmpDirs: string[] = []; + + async function createTempDirWithConfig(configName: string): Promise { + const dir = await fs.mkdtemp(path.join('tests', 'tmp-configs-')); + tmpDirs.push(dir); + + const outputName = outputConfigName(configName); + await fs.copyFile(path.join('tests', 'factories', configName), path.join(dir, outputName)); + + return dir; + } + + after(() => { + tmpDirs.forEach(async (dir) => { + // await fs.rm(dir, { recursive: true, force: true }); + }); + }); + + it('should add next-video to the next.config.js file', async () => { + const dirPath = await createTempDirWithConfig('next.config.js'); + await updateNextConfigFile(dirPath); + + const updatedContents = await fs.readFile(path.join(dirPath, 'next.config.js'), 'utf-8'); + + assert(updatedContents.includes('next-video')); + }); + + it('should add next-video to the next.config.mjs file', async () => { + const dirPath = await createTempDirWithConfig('next.config.mjs'); + await updateNextConfigFile(dirPath); + + console.log({ dirPath }); + + const updatedContents = await fs.readFile(path.join(dirPath, 'next.config.mjs'), 'utf-8'); + + assert(updatedContents.includes('next-video')); + }); +}); diff --git a/tests/factories/next.config.js b/tests/factories/next.config.js new file mode 100644 index 0000000..e693a31 --- /dev/null +++ b/tests/factories/next.config.js @@ -0,0 +1,8 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + /* config options here */ +}; + +module.exports = nextConfig; diff --git a/tests/factories/next.config.mjs b/tests/factories/next.config.mjs new file mode 100644 index 0000000..3c7f4da --- /dev/null +++ b/tests/factories/next.config.mjs @@ -0,0 +1,8 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/tests/factories/next.function.config.js b/tests/factories/next.function.config.js new file mode 100644 index 0000000..2332239 --- /dev/null +++ b/tests/factories/next.function.config.js @@ -0,0 +1,9 @@ +module.exports = (phase, { defaultConfig }) => { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { + /* config options here */ + }; + return nextConfig; +}; diff --git a/tests/factories/next.promise.config.js b/tests/factories/next.promise.config.js new file mode 100644 index 0000000..1d03068 --- /dev/null +++ b/tests/factories/next.promise.config.js @@ -0,0 +1,9 @@ +module.exports = async (phase, { defaultConfig }) => { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { + ...defaultConfig, + }; + return nextConfig; +}; diff --git a/tests/cli/factories/package.dep.json b/tests/factories/package.dep.json similarity index 100% rename from tests/cli/factories/package.dep.json rename to tests/factories/package.dep.json diff --git a/tests/cli/factories/package.devDep.json b/tests/factories/package.devDep.json similarity index 100% rename from tests/cli/factories/package.devDep.json rename to tests/factories/package.devDep.json diff --git a/tests/cli/factories/package.none.json b/tests/factories/package.none.json similarity index 100% rename from tests/cli/factories/package.none.json rename to tests/factories/package.none.json diff --git a/tests/with-next-video.test.ts b/tests/with-next-video.test.ts new file mode 100644 index 0000000..d081925 --- /dev/null +++ b/tests/with-next-video.test.ts @@ -0,0 +1,50 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; + +import withNextVideo from '../src/with-next-video.js'; + +describe('withNextVideo', () => { + it('should handle nextConfig being a function', async () => { + const nextConfig = (phase, { defaultConfig }) => { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { + ...defaultConfig, + }; + return nextConfig; + }; + + const result = await withNextVideo(nextConfig); + + const configResult = await result('phase', { defaultConfig: {} }); + + assert(typeof configResult.webpack === 'function'); + }); + + it('should handle nextConfig being a promise', async () => { + const nextConfig = async (phase, { defaultConfig }) => { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { + ...defaultConfig, + }; + return nextConfig; + }; + + const result = await withNextVideo(nextConfig); + + const configResult = await result('phase', { defaultConfig: {} }); + + assert(typeof configResult.webpack === 'function'); + }); + + it('should handle nextConfig being an object', async () => { + const nextConfig = {}; + + const result = await withNextVideo(nextConfig); + + assert(typeof result.webpack === 'function'); + }); +});