Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: type hint for env in dev mode #67157

Merged
merged 31 commits into from
Jul 10, 2024

Conversation

devjiwonchoi
Copy link
Member

@devjiwonchoi devjiwonchoi commented Jun 24, 2024

Port of #48029

Why?

Users can benefit the autocomplete and type-checking environment variable experience.

Autocompletion

As we have multiple entries for loading the env (e.g., env.local, .env, etc.), the user has to check each time if the proper environment exists and type its full name.

Type-checking

The default type of process.env['key'] from @types/node is string | undefined.

This adds extra steps when accessing env, which requires a string value.

The extra steps could include type alias, or explicitly setting as not-null.

// requires param with string type
new Client({ 
  secret: process.env.SOME_SECRET! // explicit not-null
  apiKey: process.env.SOME_API_KEY as string // type alias
})

What?

This PR added experimental.typedEnv that generates a .d.ts file on Dev Server.
It generates from the loaded env files, except production specific files like .env.production.local.
It is because the Dev Server does not load the production-specific env files.

How?

When starting the dev server, we read off the env files and next config.

If we validate that there is experimental.typedEnv set as true in next config, we generate a env.d.ts file inside the distDir (.next by default).

If there is a change in the env files, we rewrite the env.d.ts file.

Closes NEXT-542

devjiwonchoi and others added 2 commits June 24, 2024 19:53
Co-authored-by: Shu Ding <g@shud.in>
@ijjk
Copy link
Member

ijjk commented Jun 24, 2024

Tests Passed

@ijjk
Copy link
Member

ijjk commented Jun 24, 2024

Stats from current PR

Default Build
General Overall increase ⚠️
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
buildDuration 17s 14.5s N/A
buildDurationCached 8.4s 6.7s N/A
nodeModulesSize 359 MB 359 MB ⚠️ +22.4 kB
nextStartRea..uration (ms) 418ms 426ms N/A
Client Bundles (main, webpack)
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
1780.HASH.js gzip 167 B 167 B
5453-HASH.js gzip 35.8 kB 35.8 kB N/A
7514-HASH.js gzip 5.06 kB 5.05 kB N/A
a7a62840-HASH.js gzip 51.7 kB 51.7 kB N/A
framework-HASH.js gzip 56.7 kB 56.7 kB N/A
main-app-HASH.js gzip 221 B 222 B N/A
main-HASH.js gzip 32.3 kB 32.3 kB N/A
webpack-HASH.js gzip 1.71 kB 1.71 kB
Overall change 1.88 kB 1.88 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
_app-HASH.js gzip 193 B 193 B
_error-HASH.js gzip 192 B 192 B
amp-HASH.js gzip 509 B 510 B N/A
css-HASH.js gzip 341 B 341 B
dynamic-HASH.js gzip 2.52 kB 2.52 kB
edge-ssr-HASH.js gzip 264 B 266 B N/A
head-HASH.js gzip 362 B 363 B N/A
hooks-HASH.js gzip 391 B 390 B N/A
image-HASH.js gzip 4.26 kB 4.26 kB
index-HASH.js gzip 268 B 268 B
link-HASH.js gzip 2.69 kB 2.69 kB N/A
routerDirect..HASH.js gzip 326 B 325 B N/A
script-HASH.js gzip 396 B 397 B N/A
withRouter-HASH.js gzip 320 B 321 B N/A
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 7.89 kB 7.89 kB
Client Build Manifests
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
_buildManifest.js gzip 485 B 483 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
index.html gzip 522 B 524 B N/A
link.html gzip 537 B 538 B N/A
withRouter.html gzip 520 B 520 B
Overall change 520 B 520 B
Edge SSR bundle Size
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
edge-ssr.js gzip 127 kB 127 kB
page.js gzip 166 kB 166 kB N/A
Overall change 127 kB 127 kB
Middleware size
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
middleware-b..fest.js gzip 660 B 660 B
middleware-r..fest.js gzip 156 B 155 B N/A
middleware.js gzip 29.5 kB 29.5 kB N/A
edge-runtime..pack.js gzip 1.03 kB 1.03 kB
Overall change 1.69 kB 1.69 kB
Next Runtimes
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
app-page-exp...dev.js gzip 183 kB 183 kB
app-page-exp..prod.js gzip 112 kB 112 kB
app-page-tur..prod.js gzip 123 kB 123 kB
app-page-tur..prod.js gzip 118 kB 118 kB
app-page.run...dev.js gzip 178 kB 178 kB
app-page.run..prod.js gzip 108 kB 108 kB
app-route-ex...dev.js gzip 23.3 kB 23.3 kB
app-route-ex..prod.js gzip 18.7 kB 18.7 kB
app-route-tu..prod.js gzip 18.7 kB 18.7 kB
app-route-tu..prod.js gzip 18.6 kB 18.6 kB
app-route.ru...dev.js gzip 24.6 kB 24.6 kB
app-route.ru..prod.js gzip 18.6 kB 18.6 kB
pages-api-tu..prod.js gzip 9.55 kB 9.55 kB
pages-api.ru...dev.js gzip 9.82 kB 9.82 kB
pages-api.ru..prod.js gzip 9.55 kB 9.55 kB
pages-turbo...prod.js gzip 21.6 kB 21.6 kB
pages.runtim...dev.js gzip 22.1 kB 22.1 kB
pages.runtim..prod.js gzip 21.6 kB 21.6 kB
server.runti..prod.js gzip 51.7 kB 51.7 kB N/A
Overall change 1.04 MB 1.04 MB
build cache
vercel/next.js canary devjiwonchoi/next.js feat/type-check-env Change
0.pack gzip 1.67 MB 1.67 MB N/A
index.pack gzip 132 kB 132 kB N/A
Overall change 0 B 0 B
Diff details
Diff for page.js

Diff too large to display

Diff for middleware.js

Diff too large to display

Diff for edge-ssr.js

Diff too large to display

Diff for image-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [8358],
   {
-    /***/ 1362: /***/ (
+    /***/ 9618: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/image",
         function () {
-          return __webpack_require__(2160);
+          return __webpack_require__(699);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 537: /***/ (module, exports, __webpack_require__) => {
+    /***/ 9451: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -40,15 +40,15 @@
         __webpack_require__(3537)
       );
       const _head = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(7092)
+        __webpack_require__(6490)
       );
-      const _getimgprops = __webpack_require__(9834);
-      const _imageconfig = __webpack_require__(5676);
-      const _imageconfigcontextsharedruntime = __webpack_require__(387);
-      const _warnonce = __webpack_require__(451);
-      const _routercontextsharedruntime = __webpack_require__(5357);
+      const _getimgprops = __webpack_require__(3646);
+      const _imageconfig = __webpack_require__(535);
+      const _imageconfigcontextsharedruntime = __webpack_require__(4724);
+      const _warnonce = __webpack_require__(6321);
+      const _routercontextsharedruntime = __webpack_require__(1759);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(3945)
+        __webpack_require__(1882)
       );
       // This is replaced by webpack define plugin
       const configEnv = {
@@ -376,7 +376,7 @@
       /***/
     },
 
-    /***/ 9834: /***/ (
+    /***/ 3646: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -392,9 +392,9 @@
           return getImgProps;
         },
       });
-      const _warnonce = __webpack_require__(451);
-      const _imageblursvg = __webpack_require__(3547);
-      const _imageconfig = __webpack_require__(5676);
+      const _warnonce = __webpack_require__(6321);
+      const _imageblursvg = __webpack_require__(8297);
+      const _imageconfig = __webpack_require__(535);
       const VALID_LOADING_VALUES =
         /* unused pure expression or super */ null && [
           "lazy",
@@ -766,7 +766,7 @@
       /***/
     },
 
-    /***/ 3547: /***/ (__unused_webpack_module, exports) => {
+    /***/ 8297: /***/ (__unused_webpack_module, exports) => {
       "use strict";
       /**
        * A shared function, used on both client and server, to generate a SVG blur placeholder.
@@ -821,7 +821,7 @@
       /***/
     },
 
-    /***/ 6850: /***/ (
+    /***/ 973: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -848,10 +848,10 @@
         },
       });
       const _interop_require_default = __webpack_require__(1478);
-      const _getimgprops = __webpack_require__(9834);
-      const _imagecomponent = __webpack_require__(537);
+      const _getimgprops = __webpack_require__(3646);
+      const _imagecomponent = __webpack_require__(9451);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(3945)
+        __webpack_require__(1882)
       );
       function getImageProps(imgProps) {
         const { props } = (0, _getimgprops.getImgProps)(imgProps, {
@@ -883,7 +883,7 @@
       /***/
     },
 
-    /***/ 3945: /***/ (__unused_webpack_module, exports) => {
+    /***/ 1882: /***/ (__unused_webpack_module, exports) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -918,7 +918,7 @@
       /***/
     },
 
-    /***/ 2160: /***/ (
+    /***/ 699: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -935,8 +935,8 @@
 
       // EXTERNAL MODULE: ./node_modules/.pnpm/react@19.0.0-rc-6230622a1a-20240610/node_modules/react/jsx-runtime.js
       var jsx_runtime = __webpack_require__(898);
-      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+main-repo+packages+next+next-packed.tgz_react-dom@19.0.0-rc-6230622a1a-20240610__zehaskxadtwcczqqbmt6koh6bq/node_modules/next/image.js
-      var next_image = __webpack_require__(6793);
+      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+diff-repo+packages+next+next-packed.tgz_react-dom@19.0.0-rc-6230622a1a-20240610__uulzbengwsfwhwaa2ambxampcy/node_modules/next/image.js
+      var next_image = __webpack_require__(1428);
       var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // CONCATENATED MODULE: ./pages/nextjs.png
       /* harmony default export */ const nextjs = {
         src: "/_next/static/media/nextjs.cae0b805.png",
@@ -966,12 +966,12 @@
       /***/
     },
 
-    /***/ 6793: /***/ (
+    /***/ 1428: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(6850);
+      module.exports = __webpack_require__(973);
 
       /***/
     },
@@ -981,7 +981,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [2888, 9774, 179], () =>
-      __webpack_exec__(1362)
+      __webpack_exec__(9618)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for main-HASH.js

Diff too large to display

Diff for server.runtime.prod.js
@@ -1,4 +1,4 @@
-(()=>{var e={"../next-env/dist/index.js":(e,t,r)=>{(()=>{var t={383:e=>{"use strict";e.exports.j=function(e){let t=e.ignoreProcessEnv?{}:process.env;for(let r in e.parsed){let i=Object.prototype.hasOwnProperty.call(t,r)?t[r]:e.parsed[r];e.parsed[r]=(function e(t,r,i){let n=function(e,t){let r=Array.from(e.matchAll(t));return r.length>0?r.slice(-1)[0].index:-1}(t,/(?!(?<=\\))\$/g);if(-1===n)return t;let s=t.slice(n).match(/((?!(?<=\\))\${?([\w]+)(?::-([^}\\]*))?}?)/);if(null!=s){let[,n,a,o]=s;return e(t.replace(n,r[a]||o||i.parsed[a]||""),r,i)}return t})(i,t,e).replace(/\\\$/g,"$")}for(let r in e.parsed)t[r]=e.parsed[r];return e}},234:(e,t,r)=>{let i=r(147),n=r(17),s=r(37),a=r(113),o=r(803).version,l=/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;function h(e){console.log(`[dotenv@${o}][DEBUG] ${e}`)}function d(e){return e&&e.DOTENV_KEY&&e.DOTENV_KEY.length>0?e.DOTENV_KEY:process.env.DOTENV_KEY&&process.env.DOTENV_KEY.length>0?process.env.DOTENV_KEY:""}function u(e){let t=n.resolve(process.cwd(),".env");return e&&e.path&&e.path.length>0&&(t=e.path),t.endsWith(".vault")?t:`${t}.vault`}let c={configDotenv:function(e){let t=n.resolve(process.cwd(),".env"),r="utf8",a=!!(e&&e.debug);if(e){if(null!=e.path){var o;t="~"===(o=e.path)[0]?n.join(s.homedir(),o.slice(1)):o}null!=e.encoding&&(r=e.encoding)}try{let n=c.parse(i.readFileSync(t,{encoding:r})),s=process.env;return e&&null!=e.processEnv&&(s=e.processEnv),c.populate(s,n,e),{parsed:n}}catch(e){return a&&h(`Failed to load ${t} ${e.message}`),{error:e}}},_configVault:function(e){console.log(`[dotenv@${o}][INFO] Loading env from encrypted .env.vault`);let t=c._parseVault(e),r=process.env;return e&&null!=e.processEnv&&(r=e.processEnv),c.populate(r,t,e),{parsed:t}},_parseVault:function(e){let t;let r=u(e),i=c.configDotenv({path:r});if(!i.parsed)throw Error(`MISSING_DATA: Cannot parse ${r} for an unknown reason`);let n=d(e).split(","),s=n.length;for(let e=0;e<s;e++)try{let r=n[e].trim(),s=function(e,t){let r;try{r=new URL(t)}catch(e){if("ERR_INVALID_URL"===e.code)throw Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development");throw e}let i=r.password;if(!i)throw Error("INVALID_DOTENV_KEY: Missing key part");let n=r.searchParams.get("environment");if(!n)throw Error("INVALID_DOTENV_KEY: Missing environment part");let s=`DOTENV_VAULT_${n.toUpperCase()}`,a=e.parsed[s];if(!a)throw Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${s} in your .env.vault file.`);return{ciphertext:a,key:i}}(i,r);t=c.decrypt(s.ciphertext,s.key);break}catch(t){if(e+1>=s)throw t}return c.parse(t)},config:function(e){let t=u(e);if(0===d(e).length)return c.configDotenv(e);if(!i.existsSync(t)){var r;return r=`You set DOTENV_KEY but you are missing a .env.vault file at ${t}. Did you forget to build it?`,console.log(`[dotenv@${o}][WARN] ${r}`),c.configDotenv(e)}return c._configVault(e)},decrypt:function(e,t){let r=Buffer.from(t.slice(-64),"hex"),i=Buffer.from(e,"base64"),n=i.slice(0,12),s=i.slice(-16);i=i.slice(12,-16);try{let e=a.createDecipheriv("aes-256-gcm",r,n);return e.setAuthTag(s),`${e.update(i)}${e.final()}`}catch(i){let e=i instanceof RangeError,t="Invalid key length"===i.message,r="Unsupported state or unable to authenticate data"===i.message;if(e||t)throw Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");if(r)throw Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");throw console.error("Error: ",i.code),console.error("Error: ",i.message),i}},parse:function(e){let t;let r={},i=e.toString();for(i=i.replace(/\r\n?/gm,"\n");null!=(t=l.exec(i));){let e=t[1],i=t[2]||"",n=(i=i.trim())[0];i=i.replace(/^(['"`])([\s\S]*)\1$/gm,"$2"),'"'===n&&(i=(i=i.replace(/\\n/g,"\n")).replace(/\\r/g,"\r")),r[e]=i}return r},populate:function(e,t,r={}){let i=!!(r&&r.debug),n=!!(r&&r.override);if("object"!=typeof t)throw Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");for(let r of Object.keys(t))Object.prototype.hasOwnProperty.call(e,r)?(!0===n&&(e[r]=t[r]),i&&(!0===n?h(`"${r}" is already defined and WAS overwritten`):h(`"${r}" is already defined and was NOT overwritten`))):e[r]=t[r]}};e.exports.configDotenv=c.configDotenv,e.exports._configVault=c._configVault,e.exports._parseVault=c._parseVault,e.exports.config=c.config,e.exports.decrypt=c.decrypt,e.exports.parse=c.parse,e.exports.populate=c.populate,e.exports=c},113:e=>{"use strict";e.exports=r("crypto")},147:e=>{"use strict";e.exports=r("fs")},37:e=>{"use strict";e.exports=r("os")},17:e=>{"use strict";e.exports=r("path")},803:e=>{"use strict";e.exports=JSON.parse('{"name":"dotenv","version":"16.3.1","description":"Loads environment variables from .env file","main":"lib/main.js","types":"lib/main.d.ts","exports":{".":{"types":"./lib/main.d.ts","require":"./lib/main.js","default":"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},"scripts":{"dts-check":"tsc --project tests/types/tsconfig.json","lint":"standard","lint-readme":"standard-markdown","pretest":"npm run lint && npm run dts-check","test":"tap tests/*.js --100 -Rspec","prerelease":"npm test","release":"standard-version"},"repository":{"type":"git","url":"git://github.com/motdotla/dotenv.git"},"funding":"https://github.com/motdotla/dotenv?sponsor=1","keywords":["dotenv","env",".env","environment","variables","config","settings"],"readmeFilename":"README.md","license":"BSD-2-Clause","devDependencies":{"@definitelytyped/dtslint":"^0.0.133","@types/node":"^18.11.3","decache":"^4.6.1","sinon":"^14.0.1","standard":"^17.0.0","standard-markdown":"^7.1.0","standard-version":"^9.5.0","tap":"^16.3.0","tar":"^6.1.11","typescript":"^4.8.4"},"engines":{"node":">=12"},"browser":{"fs":false}}')}},i={};function n(e){var r=i[e];if(void 0!==r)return r.exports;var s=i[e]={exports:{}},a=!0;try{t[e](s,s.exports,n),a=!1}finally{a&&delete i[e]}return s.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.ab=__dirname+"/";var s={};(()=>{"use strict";let e,t;n.r(s),n.d(s,{initialEnv:()=>e,updateInitialEnv:()=>d,processEnv:()=>c,resetEnv:()=>p,loadEnvConfig:()=>f});var r=n(147);n.n(r);var i=n(17);n.n(i);var a=n(234);n.n(a);var o=n(383);let l=[],h=[];function d(t){Object.assign(e||{},t)}function u(e){Object.keys(process.env).forEach(t=>{t.startsWith("__NEXT_PRIVATE")||void 0!==e[t]&&""!==e[t]||delete process.env[t]}),Object.entries(e).forEach(([e,t])=>{process.env[e]=t})}function c(t,r,n=console,s=!1,l){var d;if(e||(e=Object.assign({},process.env)),!s&&(process.env.__NEXT_PROCESSED_ENV||0===t.length))return process.env;process.env.__NEXT_PROCESSED_ENV="true";let u=Object.assign({},e),p={};for(let e of t)try{let t={};for(let r of(t.parsed=a.parse(e.contents),(t=(0,o.j)(t)).parsed&&!h.some(t=>t.contents===e.contents&&t.path===e.path)&&(null==l||l(e.path)),Object.keys(t.parsed||{})))void 0===p[r]&&void 0===u[r]&&(p[r]=null===(d=t.parsed)||void 0===d?void 0:d[r])}catch(t){n.error(`Failed to load env from ${i.join(r||"",e.path)}`,t)}return Object.assign(process.env,p)}function p(){e&&u(e)}function f(n,s,a=console,o=!1,d){if(e||(e=Object.assign({},process.env)),t&&!o)return{combinedEnv:t,loadedEnvFiles:l};u(e),h=l,l=[];let p=s?"development":"production";for(let e of[`.env.${p}.local`,"test"!==p&&".env.local",`.env.${p}`,".env"].filter(Boolean)){let t=i.join(n,e);try{if(!r.statSync(t).isFile())continue;let i=r.readFileSync(t,"utf8");l.push({path:e,contents:i})}catch(t){"ENOENT"!==t.code&&a.error(`Failed to load env from ${e}`,t)}}return{combinedEnv:t=c(l,n,a,o,d),loadedEnvFiles:l}}})(),e.exports=s})()},"./dist/compiled/@edge-runtime/cookies/index.js":e=>{"use strict";var t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,s={};function a(e){var t;let r=["path"in e&&e.path&&`Path=${e.path}`,"expires"in e&&(e.expires||0===e.expires)&&`Expires=${("number"==typeof e.expires?new Date(e.expires):e.expires).toUTCString()}`,"maxAge"in e&&"number"==typeof e.maxAge&&`Max-Age=${e.maxAge}`,"domain"in e&&e.domain&&`Domain=${e.domain}`,"secure"in e&&e.secure&&"Secure","httpOnly"in e&&e.httpOnly&&"HttpOnly","sameSite"in e&&e.sameSite&&`SameSite=${e.sameSite}`,"partitioned"in e&&e.partitioned&&"Partitioned","priority"in e&&e.priority&&`Priority=${e.priority}`].filter(Boolean),i=`${e.name}=${encodeURIComponent(null!=(t=e.value)?t:"")}`;return 0===r.length?i:`${i}; ${r.join("; ")}`}function o(e){let t=new Map;for(let r of e.split(/; */)){if(!r)continue;let e=r.indexOf("=");if(-1===e){t.set(r,"true");continue}let[i,n]=[r.slice(0,e),r.slice(e+1)];try{t.set(i,decodeURIComponent(null!=n?n:"true"))}catch{}}return t}function l(e){var t,r;if(!e)return;let[[i,n],...s]=o(e),{domain:a,expires:l,httponly:u,maxage:c,path:p,samesite:f,secure:m,partitioned:g,priority:v}=Object.fromEntries(s.map(([e,t])=>[e.toLowerCase(),t]));return function(e){let t={};for(let r in e)e[r]&&(t[r]=e[r]);return t}({name:i,value:decodeURIComponent(n),domain:a,...l&&{expires:new Date(l)},...u&&{httpOnly:!0},..."string"==typeof c&&{maxAge:Number(c)},path:p,...f&&{sameSite:h.includes(t=(t=f).toLowerCase())?t:void 0},...m&&{secure:!0},...v&&{priority:d.includes(r=(r=v).toLowerCase())?r:void 0},...g&&{partitioned:!0}})}((e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})})(s,{RequestCookies:()=>u,ResponseCookies:()=>c,parseCookie:()=>o,parseSetCookie:()=>l,stringifyCookie:()=>a}),e.exports=((e,s,a,o)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let l of i(s))n.call(e,l)||l===a||t(e,l,{get:()=>s[l],enumerable:!(o=r(s,l))||o.enumerable});return e})(t({},"__esModule",{value:!0}),s);var h=["strict","lax","none"],d=["low","medium","high"],u=class{constructor(e){this._parsed=new Map,this._headers=e;let t=e.get("cookie");if(t)for(let[e,r]of o(t))this._parsed.set(e,{name:e,value:r})}[Symbol.iterator](){return this._parsed[Symbol.iterator]()}get size(){return this._parsed.size}get(...e){let t="string"==typeof e[0]?e[0]:e[0].name;return this._parsed.get(t)}getAll(...e){var t;let r=Array.from(this._parsed);if(!e.length)return r.map(([e,t])=>t);let i="string"==typeof e[0]?e[0]:null==(t=e[0])?void 0:t.name;return r.filter(([e])=>e===i).map(([e,t])=>t)}has(e){return this._parsed.has(e)}set(...e){let[t,r]=1===e.length?[e[0].name,e[0].value]:e,i=this._parsed;return i.set(t,{name:t,value:r}),this._headers.set("cookie",Array.from(i).map(([e,t])=>a(t)).join("; ")),this}delete(e){let t=this._parsed,r=Array.isArray(e)?e.map(e=>t.delete(e)):t.delete(e);return this._headers.set("cookie",Array.from(t).map(([e,t])=>a(t)).join("; ")),r}clear(){return this.delete(Array.from(this._parsed.keys())),this}[Symbol.for("edge-runtime.inspect.custom")](){return`RequestCookies ${JSON.stringify(Object.fromEntries(this._parsed))}`}toString(){return[...this._parsed.values()].map(e=>`${e.name}=${encodeURIComponent(e.value)}`).join("; ")}},c=class{constructor(e){var t,r,i;this._parsed=new Map,this._headers=e;let n=null!=(i=null!=(r=null==(t=e.getSetCookie)?void 0:t.call(e))?r:e.get("set-cookie"))?i:[];for(let e of Array.isArray(n)?n:function(e){if(!e)return[];var t,r,i,n,s,a=[],o=0;function l(){for(;o<e.length&&/\s/.test(e.charAt(o));)o+=1;return o<e.length}for(;o<e.length;){for(t=o,s=!1;l();)if(","===(r=e.charAt(o))){for(i=o,o+=1,l(),n=o;o<e.length&&"="!==(r=e.charAt(o))&&";"!==r&&","!==r;)o+=1;o<e.length&&"="===e.charAt(o)?(s=!0,o=n,a.push(e.substring(t,i)),t=o):o=i+1}else o+=1;(!s||o>=e.length)&&a.push(e.substring(t,e.length))}return a}(n)){let t=l(e);t&&this._parsed.set(t.name,t)}}get(...e){let t="string"==typeof e[0]?e[0]:e[0].name;return this._parsed.get(t)}getAll(...e){var t;let r=Array.from(this._parsed.values());if(!e.length)return r;let i="string"==typeof e[0]?e[0]:null==(t=e[0])?void 0:t.name;return r.filter(e=>e.name===i)}has(e){return this._parsed.has(e)}set(...e){let[t,r,i]=1===e.length?[e[0].name,e[0].value,e[0]]:e,n=this._parsed;return n.set(t,function(e={name:"",value:""}){return"number"==typeof e.expires&&(e.expires=new Date(e.expires)),e.maxAge&&(e.expires=new Date(Date.now()+1e3*e.maxAge)),(null===e.path||void 0===e.path)&&(e.path="/"),e}({name:t,value:r,...i})),function(e,t){for(let[,r]of(t.delete("set-cookie"),e)){let e=a(r);t.append("set-cookie",e)}}(n,this._headers),this}delete(...e){let[t,r,i]="string"==typeof e[0]?[e[0]]:[e[0].name,e[0].path,e[0].domain];return this.set({name:t,path:r,domain:i,value:"",expires:new Date(0)})}[Symbol.for("edge-runtime.inspect.custom")](){return`ResponseCookies ${JSON.stringify(Object.fromEntries(this._parsed))}`}toString(){return[...this._parsed.values()].map(a).join("; ")}}},"./dist/compiled/cookie/index.js":e=>{(()=>{"use strict";"undefined"!=typeof __nccwpck_require__&&(__nccwpck_require__.ab=__dirname+"/");var t={};(()=>{/*!
+(()=>{var e={"../next-env/dist/index.js":(e,t,r)=>{(()=>{var t={383:e=>{"use strict";e.exports.j=function(e){let t=e.ignoreProcessEnv?{}:process.env;for(let r in e.parsed){let i=Object.prototype.hasOwnProperty.call(t,r)?t[r]:e.parsed[r];e.parsed[r]=(function e(t,r,i){let n=function(e,t){let r=Array.from(e.matchAll(t));return r.length>0?r.slice(-1)[0].index:-1}(t,/(?!(?<=\\))\$/g);if(-1===n)return t;let s=t.slice(n).match(/((?!(?<=\\))\${?([\w]+)(?::-([^}\\]*))?}?)/);if(null!=s){let[,n,a,o]=s;return e(t.replace(n,r[a]||o||i.parsed[a]||""),r,i)}return t})(i,t,e).replace(/\\\$/g,"$")}for(let r in e.parsed)t[r]=e.parsed[r];return e}},234:(e,t,r)=>{let i=r(147),n=r(17),s=r(37),a=r(113),o=r(803).version,l=/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;function h(e){console.log(`[dotenv@${o}][DEBUG] ${e}`)}function d(e){return e&&e.DOTENV_KEY&&e.DOTENV_KEY.length>0?e.DOTENV_KEY:process.env.DOTENV_KEY&&process.env.DOTENV_KEY.length>0?process.env.DOTENV_KEY:""}function u(e){let t=n.resolve(process.cwd(),".env");return e&&e.path&&e.path.length>0&&(t=e.path),t.endsWith(".vault")?t:`${t}.vault`}let c={configDotenv:function(e){let t=n.resolve(process.cwd(),".env"),r="utf8",a=!!(e&&e.debug);if(e){if(null!=e.path){var o;t="~"===(o=e.path)[0]?n.join(s.homedir(),o.slice(1)):o}null!=e.encoding&&(r=e.encoding)}try{let n=c.parse(i.readFileSync(t,{encoding:r})),s=process.env;return e&&null!=e.processEnv&&(s=e.processEnv),c.populate(s,n,e),{parsed:n}}catch(e){return a&&h(`Failed to load ${t} ${e.message}`),{error:e}}},_configVault:function(e){console.log(`[dotenv@${o}][INFO] Loading env from encrypted .env.vault`);let t=c._parseVault(e),r=process.env;return e&&null!=e.processEnv&&(r=e.processEnv),c.populate(r,t,e),{parsed:t}},_parseVault:function(e){let t;let r=u(e),i=c.configDotenv({path:r});if(!i.parsed)throw Error(`MISSING_DATA: Cannot parse ${r} for an unknown reason`);let n=d(e).split(","),s=n.length;for(let e=0;e<s;e++)try{let r=n[e].trim(),s=function(e,t){let r;try{r=new URL(t)}catch(e){if("ERR_INVALID_URL"===e.code)throw Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development");throw e}let i=r.password;if(!i)throw Error("INVALID_DOTENV_KEY: Missing key part");let n=r.searchParams.get("environment");if(!n)throw Error("INVALID_DOTENV_KEY: Missing environment part");let s=`DOTENV_VAULT_${n.toUpperCase()}`,a=e.parsed[s];if(!a)throw Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${s} in your .env.vault file.`);return{ciphertext:a,key:i}}(i,r);t=c.decrypt(s.ciphertext,s.key);break}catch(t){if(e+1>=s)throw t}return c.parse(t)},config:function(e){let t=u(e);if(0===d(e).length)return c.configDotenv(e);if(!i.existsSync(t)){var r;return r=`You set DOTENV_KEY but you are missing a .env.vault file at ${t}. Did you forget to build it?`,console.log(`[dotenv@${o}][WARN] ${r}`),c.configDotenv(e)}return c._configVault(e)},decrypt:function(e,t){let r=Buffer.from(t.slice(-64),"hex"),i=Buffer.from(e,"base64"),n=i.slice(0,12),s=i.slice(-16);i=i.slice(12,-16);try{let e=a.createDecipheriv("aes-256-gcm",r,n);return e.setAuthTag(s),`${e.update(i)}${e.final()}`}catch(i){let e=i instanceof RangeError,t="Invalid key length"===i.message,r="Unsupported state or unable to authenticate data"===i.message;if(e||t)throw Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");if(r)throw Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");throw console.error("Error: ",i.code),console.error("Error: ",i.message),i}},parse:function(e){let t;let r={},i=e.toString();for(i=i.replace(/\r\n?/gm,"\n");null!=(t=l.exec(i));){let e=t[1],i=t[2]||"",n=(i=i.trim())[0];i=i.replace(/^(['"`])([\s\S]*)\1$/gm,"$2"),'"'===n&&(i=(i=i.replace(/\\n/g,"\n")).replace(/\\r/g,"\r")),r[e]=i}return r},populate:function(e,t,r={}){let i=!!(r&&r.debug),n=!!(r&&r.override);if("object"!=typeof t)throw Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");for(let r of Object.keys(t))Object.prototype.hasOwnProperty.call(e,r)?(!0===n&&(e[r]=t[r]),i&&(!0===n?h(`"${r}" is already defined and WAS overwritten`):h(`"${r}" is already defined and was NOT overwritten`))):e[r]=t[r]}};e.exports.configDotenv=c.configDotenv,e.exports._configVault=c._configVault,e.exports._parseVault=c._parseVault,e.exports.config=c.config,e.exports.decrypt=c.decrypt,e.exports.parse=c.parse,e.exports.populate=c.populate,e.exports=c},113:e=>{"use strict";e.exports=r("crypto")},147:e=>{"use strict";e.exports=r("fs")},37:e=>{"use strict";e.exports=r("os")},17:e=>{"use strict";e.exports=r("path")},803:e=>{"use strict";e.exports=JSON.parse('{"name":"dotenv","version":"16.3.1","description":"Loads environment variables from .env file","main":"lib/main.js","types":"lib/main.d.ts","exports":{".":{"types":"./lib/main.d.ts","require":"./lib/main.js","default":"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},"scripts":{"dts-check":"tsc --project tests/types/tsconfig.json","lint":"standard","lint-readme":"standard-markdown","pretest":"npm run lint && npm run dts-check","test":"tap tests/*.js --100 -Rspec","prerelease":"npm test","release":"standard-version"},"repository":{"type":"git","url":"git://github.com/motdotla/dotenv.git"},"funding":"https://github.com/motdotla/dotenv?sponsor=1","keywords":["dotenv","env",".env","environment","variables","config","settings"],"readmeFilename":"README.md","license":"BSD-2-Clause","devDependencies":{"@definitelytyped/dtslint":"^0.0.133","@types/node":"^18.11.3","decache":"^4.6.1","sinon":"^14.0.1","standard":"^17.0.0","standard-markdown":"^7.1.0","standard-version":"^9.5.0","tap":"^16.3.0","tar":"^6.1.11","typescript":"^4.8.4"},"engines":{"node":">=12"},"browser":{"fs":false}}')}},i={};function n(e){var r=i[e];if(void 0!==r)return r.exports;var s=i[e]={exports:{}},a=!0;try{t[e](s,s.exports,n),a=!1}finally{a&&delete i[e]}return s.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.ab=__dirname+"/";var s={};(()=>{"use strict";let e,t,r;n.r(s),n.d(s,{initialEnv:()=>e,updateInitialEnv:()=>u,processEnv:()=>p,resetEnv:()=>f,loadEnvConfig:()=>m});var i=n(147);n.n(i);var a=n(17);n.n(a);var o=n(234);n.n(o);var l=n(383);let h=[],d=[];function u(t){Object.assign(e||{},t)}function c(e){Object.keys(process.env).forEach(t=>{t.startsWith("__NEXT_PRIVATE")||void 0!==e[t]&&""!==e[t]||delete process.env[t]}),Object.entries(e).forEach(([e,t])=>{process.env[e]=t})}function p(t,r,i=console,n=!1,s){var h;if(e||(e=Object.assign({},process.env)),!n&&(process.env.__NEXT_PROCESSED_ENV||0===t.length))return[process.env];process.env.__NEXT_PROCESSED_ENV="true";let u=Object.assign({},e),c={};for(let e of t)try{let t={};for(let r of(t.parsed=o.parse(e.contents),(t=(0,l.j)(t)).parsed&&!d.some(t=>t.contents===e.contents&&t.path===e.path)&&(null==s||s(e.path)),Object.keys(t.parsed||{})))void 0===c[r]&&void 0===u[r]&&(c[r]=null===(h=t.parsed)||void 0===h?void 0:h[r])}catch(t){i.error(`Failed to load env from ${a.join(r||"",e.path)}`,t)}return[Object.assign(process.env,c),c]}function f(){e&&c(e)}function m(n,s,o=console,l=!1,u){if(e||(e=Object.assign({},process.env)),t&&!l)return{combinedEnv:t,parsedEnv:r,loadedEnvFiles:h};c(e),d=h,h=[];let f=s?"development":"production";for(let e of[`.env.${f}.local`,"test"!==f&&".env.local",`.env.${f}`,".env"].filter(Boolean)){let t=a.join(n,e);try{if(!i.statSync(t).isFile())continue;let r=i.readFileSync(t,"utf8");h.push({path:e,contents:r})}catch(t){"ENOENT"!==t.code&&o.error(`Failed to load env from ${e}`,t)}}return[t,r]=p(h,n,o,l,u),{combinedEnv:t,parsedEnv:r,loadedEnvFiles:h}}})(),e.exports=s})()},"./dist/compiled/@edge-runtime/cookies/index.js":e=>{"use strict";var t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,s={};function a(e){var t;let r=["path"in e&&e.path&&`Path=${e.path}`,"expires"in e&&(e.expires||0===e.expires)&&`Expires=${("number"==typeof e.expires?new Date(e.expires):e.expires).toUTCString()}`,"maxAge"in e&&"number"==typeof e.maxAge&&`Max-Age=${e.maxAge}`,"domain"in e&&e.domain&&`Domain=${e.domain}`,"secure"in e&&e.secure&&"Secure","httpOnly"in e&&e.httpOnly&&"HttpOnly","sameSite"in e&&e.sameSite&&`SameSite=${e.sameSite}`,"partitioned"in e&&e.partitioned&&"Partitioned","priority"in e&&e.priority&&`Priority=${e.priority}`].filter(Boolean),i=`${e.name}=${encodeURIComponent(null!=(t=e.value)?t:"")}`;return 0===r.length?i:`${i}; ${r.join("; ")}`}function o(e){let t=new Map;for(let r of e.split(/; */)){if(!r)continue;let e=r.indexOf("=");if(-1===e){t.set(r,"true");continue}let[i,n]=[r.slice(0,e),r.slice(e+1)];try{t.set(i,decodeURIComponent(null!=n?n:"true"))}catch{}}return t}function l(e){var t,r;if(!e)return;let[[i,n],...s]=o(e),{domain:a,expires:l,httponly:u,maxage:c,path:p,samesite:f,secure:m,partitioned:g,priority:v}=Object.fromEntries(s.map(([e,t])=>[e.toLowerCase(),t]));return function(e){let t={};for(let r in e)e[r]&&(t[r]=e[r]);return t}({name:i,value:decodeURIComponent(n),domain:a,...l&&{expires:new Date(l)},...u&&{httpOnly:!0},..."string"==typeof c&&{maxAge:Number(c)},path:p,...f&&{sameSite:h.includes(t=(t=f).toLowerCase())?t:void 0},...m&&{secure:!0},...v&&{priority:d.includes(r=(r=v).toLowerCase())?r:void 0},...g&&{partitioned:!0}})}((e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})})(s,{RequestCookies:()=>u,ResponseCookies:()=>c,parseCookie:()=>o,parseSetCookie:()=>l,stringifyCookie:()=>a}),e.exports=((e,s,a,o)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let l of i(s))n.call(e,l)||l===a||t(e,l,{get:()=>s[l],enumerable:!(o=r(s,l))||o.enumerable});return e})(t({},"__esModule",{value:!0}),s);var h=["strict","lax","none"],d=["low","medium","high"],u=class{constructor(e){this._parsed=new Map,this._headers=e;let t=e.get("cookie");if(t)for(let[e,r]of o(t))this._parsed.set(e,{name:e,value:r})}[Symbol.iterator](){return this._parsed[Symbol.iterator]()}get size(){return this._parsed.size}get(...e){let t="string"==typeof e[0]?e[0]:e[0].name;return this._parsed.get(t)}getAll(...e){var t;let r=Array.from(this._parsed);if(!e.length)return r.map(([e,t])=>t);let i="string"==typeof e[0]?e[0]:null==(t=e[0])?void 0:t.name;return r.filter(([e])=>e===i).map(([e,t])=>t)}has(e){return this._parsed.has(e)}set(...e){let[t,r]=1===e.length?[e[0].name,e[0].value]:e,i=this._parsed;return i.set(t,{name:t,value:r}),this._headers.set("cookie",Array.from(i).map(([e,t])=>a(t)).join("; ")),this}delete(e){let t=this._parsed,r=Array.isArray(e)?e.map(e=>t.delete(e)):t.delete(e);return this._headers.set("cookie",Array.from(t).map(([e,t])=>a(t)).join("; ")),r}clear(){return this.delete(Array.from(this._parsed.keys())),this}[Symbol.for("edge-runtime.inspect.custom")](){return`RequestCookies ${JSON.stringify(Object.fromEntries(this._parsed))}`}toString(){return[...this._parsed.values()].map(e=>`${e.name}=${encodeURIComponent(e.value)}`).join("; ")}},c=class{constructor(e){var t,r,i;this._parsed=new Map,this._headers=e;let n=null!=(i=null!=(r=null==(t=e.getSetCookie)?void 0:t.call(e))?r:e.get("set-cookie"))?i:[];for(let e of Array.isArray(n)?n:function(e){if(!e)return[];var t,r,i,n,s,a=[],o=0;function l(){for(;o<e.length&&/\s/.test(e.charAt(o));)o+=1;return o<e.length}for(;o<e.length;){for(t=o,s=!1;l();)if(","===(r=e.charAt(o))){for(i=o,o+=1,l(),n=o;o<e.length&&"="!==(r=e.charAt(o))&&";"!==r&&","!==r;)o+=1;o<e.length&&"="===e.charAt(o)?(s=!0,o=n,a.push(e.substring(t,i)),t=o):o=i+1}else o+=1;(!s||o>=e.length)&&a.push(e.substring(t,e.length))}return a}(n)){let t=l(e);t&&this._parsed.set(t.name,t)}}get(...e){let t="string"==typeof e[0]?e[0]:e[0].name;return this._parsed.get(t)}getAll(...e){var t;let r=Array.from(this._parsed.values());if(!e.length)return r;let i="string"==typeof e[0]?e[0]:null==(t=e[0])?void 0:t.name;return r.filter(e=>e.name===i)}has(e){return this._parsed.has(e)}set(...e){let[t,r,i]=1===e.length?[e[0].name,e[0].value,e[0]]:e,n=this._parsed;return n.set(t,function(e={name:"",value:""}){return"number"==typeof e.expires&&(e.expires=new Date(e.expires)),e.maxAge&&(e.expires=new Date(Date.now()+1e3*e.maxAge)),(null===e.path||void 0===e.path)&&(e.path="/"),e}({name:t,value:r,...i})),function(e,t){for(let[,r]of(t.delete("set-cookie"),e)){let e=a(r);t.append("set-cookie",e)}}(n,this._headers),this}delete(...e){let[t,r,i]="string"==typeof e[0]?[e[0]]:[e[0].name,e[0].path,e[0].domain];return this.set({name:t,path:r,domain:i,value:"",expires:new Date(0)})}[Symbol.for("edge-runtime.inspect.custom")](){return`ResponseCookies ${JSON.stringify(Object.fromEntries(this._parsed))}`}toString(){return[...this._parsed.values()].map(a).join("; ")}}},"./dist/compiled/cookie/index.js":e=>{(()=>{"use strict";"undefined"!=typeof __nccwpck_require__&&(__nccwpck_require__.ab=__dirname+"/");var t={};(()=>{/*!
  * cookie
  * Copyright(c) 2012-2014 Roman Shtylman
  * Copyright(c) 2015 Douglas Christopher Wilson
Commit: 98eddda

@devjiwonchoi devjiwonchoi marked this pull request as ready for review June 24, 2024 19:21
@devjiwonchoi devjiwonchoi requested review from huozhi and shuding June 24, 2024 19:36
@Netail
Copy link
Contributor

Netail commented Jun 24, 2024

As we now also get typed env variables, will we also get typed page & layout params? (Kind of like NextPage type of the pages directory, but the params attribute is generated based on the path params)

@devjiwonchoi devjiwonchoi marked this pull request as draft June 25, 2024 17:20
@ijjk ijjk added the Documentation Related to Next.js' official documentation. label Jul 4, 2024
@devjiwonchoi devjiwonchoi marked this pull request as ready for review July 4, 2024 14:09
packages/next/src/server/config-shared.ts Outdated Show resolved Hide resolved
@@ -257,6 +260,11 @@ export async function startServer(
const startServerInfo = await getStartServerInfo(dir, isDev)
envInfo = startServerInfo.envInfo
expFeatureInfo = startServerInfo.expFeatureInfo

// opt out if typedEnv is set to false
if (startServerInfo.nextConfig.typedEnv !== false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be duplicated, it will executed on setup-dev-bundler in dev. We don't need it here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup-dev-bundler runs on envChange for HMR, should I add some logic there to run initially?

x-ref: here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the one in setup-dev is going to run anyway for initial start, we don't need to call it here.
If your purpose is to do type check in build mode, then might need to parse the env file and write before build TS check started.

packages/next/src/server/lib/experimental/typed-env.ts Outdated Show resolved Hide resolved
packages/next/src/server/lib/experimental/typed-env.ts Outdated Show resolved Hide resolved
/** @type {import('next').NextConfig} */
const nextConfig = {
// enabled by default, opt out by setting to false
typedEnv: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give this at least a non-abbreviated name for readability. It's only typed once so the name can be even more descriptive.

@@ -202,12 +202,15 @@ const nextDev = async (
traceUploadUrl = options.experimentalUploadTrace
}

const distDir = path.join(dir, config.distDir ?? '.next')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aside: Shouldn't this have gotten the default value closer to where we initialize config?

Copy link
Member Author

@devjiwonchoi devjiwonchoi Jul 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

packages/next/src/server/config-shared.ts Outdated Show resolved Hide resolved
test/development/typed-env/app-dir/typed-env-app.test.ts Outdated Show resolved Hide resolved
@devjiwonchoi devjiwonchoi requested a review from eps1lon July 8, 2024 12:11
packages/next/src/server/lib/experimental/typed-env.ts Outdated Show resolved Hide resolved
@@ -257,6 +260,11 @@ export async function startServer(
const startServerInfo = await getStartServerInfo(dir, isDev)
envInfo = startServerInfo.envInfo
expFeatureInfo = startServerInfo.expFeatureInfo

// opt out if typedEnv is set to false
if (startServerInfo.nextConfig.typedEnv !== false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the one in setup-dev is going to run anyway for initial start, we don't need to call it here.
If your purpose is to do type check in build mode, then might need to parse the env file and write before build TS check started.

@eps1lon
Copy link
Member

eps1lon commented Jul 8, 2024

type-safe -> *type-checking

It's not clear to me what's type checked now, that wasn't before.

The main benefit of this feature is autocompletion of the loaded envs during development. This do not include build-time, there for production-specific envs were not included. (It does include .env.local, .env, etc.)

But the code is shipped to production as well. Why wouldn't we not want to offer autocomplete in those cases?

@devjiwonchoi
Copy link
Member Author

@eps1lon

Type-checking allows autocompletion, and validate the env was loaded (no need assumption like as string or notNull!.

Screenshot 2024-07-09 at 12 43 56 AM

Updated the description as well.

But the code is shipped to production as well.

It is because the Dev Server does not load the production-specific env files (e.g. .env.production).
I'm planning to add validation for prod on requiredEnv.

@huozhi
Copy link
Member

huozhi commented Jul 8, 2024

I think we should rename it to type hint for development. Since the PR is not covering production build checking atm, where it could bail the build when types are not matched. Could do it in the following PR.

@devjiwonchoi devjiwonchoi marked this pull request as draft July 9, 2024 16:17
Comment on lines 154 to 160
// we ensure the types directory exists on turbo
if (usingTypeScript && opts.turbo) {
const distTypesDir = path.join(distDir, 'types')
if (!fs.existsSync(distTypesDir)) {
await mkdir(distTypesDir, { recursive: true })
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huozhi Since turbo don't generate types dir like our webpack plugin does, we ensure it is generated here for further typed... features.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty env.d.ts don't affect existing envs:

Screen.Recording.2024-07-10.at.5.40.09.PM.mov

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check if types dir is created in the type creation util so we don't have to do that logic for turbopack specifically? This might be easily forgotten to update later on

@devjiwonchoi devjiwonchoi marked this pull request as ready for review July 9, 2024 18:58
@devjiwonchoi devjiwonchoi requested a review from huozhi July 9, 2024 18:58
@huozhi huozhi changed the title feat: type check for env feat: type hint for env in dev mode Jul 10, 2024
Comment on lines 154 to 160
// we ensure the types directory exists on turbo
if (usingTypeScript && opts.turbo) {
const distTypesDir = path.join(distDir, 'types')
if (!fs.existsSync(distTypesDir)) {
await mkdir(distTypesDir, { recursive: true })
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check if types dir is created in the type creation util so we don't have to do that logic for turbopack specifically? This might be easily forgotten to update later on

@shuding
Copy link
Member

shuding commented Jul 10, 2024

But the code is shipped to production as well. Why wouldn't we not want to offer autocomplete in those cases?

For prod build it's a bit more complicated. One environment variable might not be existing during build time, but only provided when running the application (ENV=1 next start).

Copy link
Member

@huozhi huozhi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, let's ship it and handle the deleting envs case in the following up PRs

@@ -149,6 +151,14 @@ async function startWatcher(opts: SetupOpts) {

const distDir = path.join(opts.dir, opts.nextConfig.distDir)

// we ensure the types directory exists here
Copy link
Member

@huozhi huozhi Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not necessary here since we're ensuring the directory anyway

Suggested change
// we ensure the types directory exists here

@devjiwonchoi devjiwonchoi merged commit 1309dee into vercel:canary Jul 10, 2024
81 checks passed
@devjiwonchoi devjiwonchoi deleted the feat/type-check-env branch July 10, 2024 13:38
@eps1lon
Copy link
Member

eps1lon commented Jul 10, 2024

Type-checking allows autocompletion, and validate the env was loaded (no need assumption like as string or notNull!.

That's not what people generally understand under type-checking. We don't check if the specified variable actually exists.

as string wasn't needed before. Every member of process.env is already a string | undefined: Playground Link.

For some reason, in Next.js, you already got a non-nullable string for arbitrary members of process.env.

@devjiwonchoi
Copy link
Member Author

devjiwonchoi commented Jul 10, 2024

Since process.env members are string | undefined we needed an extra layer to use it if required argument's type was strictly a string.

const foo = (str: string) => str
foo(process.env.FOO)
    ^^^^^^^^^^^^^^^
// Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
//  Type 'undefined' is not assignable to type 'string'.ts(2345)

foo(process.env.FOO as string)
foo(process.env.FOO!)

I think the word type-check is causing confusion. Will come up with better one.

@huozhi
Copy link
Member

huozhi commented Jul 10, 2024

ProcessEnv type should be string | undefined instead of readonly string, since nodejs native process.env could contain a key but value is undefined

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
created-by: Next.js team PRs by the Next.js team. Documentation Related to Next.js' official documentation. locked tests type: next
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants