mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
fix: do not freeze process.env in dev (#12585)
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
parent
e21c7e67fd
commit
a9373c0c9a
12 changed files with 113 additions and 81 deletions
5
.changeset/nasty-parrots-cry.md
Normal file
5
.changeset/nasty-parrots-cry.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes a case where `process.env` would be frozen despite changes made to environment variables in development
|
|
@ -155,7 +155,6 @@ function createManifest(
|
||||||
i18n: manifest?.i18n,
|
i18n: manifest?.i18n,
|
||||||
checkOrigin: false,
|
checkOrigin: false,
|
||||||
middleware: manifest?.middleware ?? middlewareInstance,
|
middleware: manifest?.middleware ?? middlewareInstance,
|
||||||
envGetSecretEnabled: false,
|
|
||||||
key: createKey(),
|
key: createKey(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,6 @@ export type SSRManifest = {
|
||||||
i18n: SSRManifestI18n | undefined;
|
i18n: SSRManifestI18n | undefined;
|
||||||
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
|
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
|
||||||
checkOrigin: boolean;
|
checkOrigin: boolean;
|
||||||
envGetSecretEnabled: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SSRManifestI18n = {
|
export type SSRManifestI18n = {
|
||||||
|
|
|
@ -559,6 +559,5 @@ function createBuildManifest(
|
||||||
checkOrigin:
|
checkOrigin:
|
||||||
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
||||||
key,
|
key,
|
||||||
envGetSecretEnabled: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,8 +274,5 @@ function buildManifest(
|
||||||
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
||||||
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
|
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
|
||||||
key: encodedKey,
|
key: encodedKey,
|
||||||
envGetSecretEnabled:
|
|
||||||
(unwrapSupportKind(settings.adapter?.supportedAstroFeatures.envGetSecret) ??
|
|
||||||
'unsupported') !== 'unsupported',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { vitePluginMiddleware } from './middleware/vite-plugin.js';
|
||||||
import { joinPaths } from './path.js';
|
import { joinPaths } from './path.js';
|
||||||
import { vitePluginServerIslands } from './server-islands/vite-plugin-server-islands.js';
|
import { vitePluginServerIslands } from './server-islands/vite-plugin-server-islands.js';
|
||||||
import { isObject } from './util.js';
|
import { isObject } from './util.js';
|
||||||
|
import { createEnvLoader } from '../env/env-loader.js';
|
||||||
|
|
||||||
type CreateViteOptions = {
|
type CreateViteOptions = {
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
|
@ -123,6 +124,7 @@ export async function createVite(
|
||||||
});
|
});
|
||||||
|
|
||||||
const srcDirPattern = glob.convertPathToPattern(fileURLToPath(settings.config.srcDir));
|
const srcDirPattern = glob.convertPathToPattern(fileURLToPath(settings.config.srcDir));
|
||||||
|
const envLoader = createEnvLoader();
|
||||||
|
|
||||||
// Start with the Vite configuration that Astro core needs
|
// Start with the Vite configuration that Astro core needs
|
||||||
const commonConfig: vite.InlineConfig = {
|
const commonConfig: vite.InlineConfig = {
|
||||||
|
@ -146,8 +148,8 @@ export async function createVite(
|
||||||
// The server plugin is for dev only and having it run during the build causes
|
// The server plugin is for dev only and having it run during the build causes
|
||||||
// the build to run very slow as the filewatcher is triggered often.
|
// the build to run very slow as the filewatcher is triggered often.
|
||||||
command === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function
|
command === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function
|
||||||
envVitePlugin({ settings }),
|
envVitePlugin({ envLoader }),
|
||||||
astroEnv({ settings, mode, sync }),
|
astroEnv({ settings, mode, sync, envLoader }),
|
||||||
markdownVitePlugin({ settings, logger }),
|
markdownVitePlugin({ settings, logger }),
|
||||||
htmlVitePlugin(),
|
htmlVitePlugin(),
|
||||||
astroPostprocessVitePlugin(),
|
astroPostprocessVitePlugin(),
|
||||||
|
|
60
packages/astro/src/env/env-loader.ts
vendored
Normal file
60
packages/astro/src/env/env-loader.ts
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { loadEnv } from 'vite';
|
||||||
|
import type { AstroConfig } from '../types/public/index.js';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
// Match valid JS variable names (identifiers), which accepts most alphanumeric characters,
|
||||||
|
// except that the first character cannot be a number.
|
||||||
|
const isValidIdentifierRe = /^[_$a-zA-Z][\w$]*$/;
|
||||||
|
|
||||||
|
function getPrivateEnv(
|
||||||
|
fullEnv: Record<string, string>,
|
||||||
|
astroConfig: AstroConfig,
|
||||||
|
): Record<string, string> {
|
||||||
|
const viteConfig = astroConfig.vite;
|
||||||
|
let envPrefixes: string[] = ['PUBLIC_'];
|
||||||
|
if (viteConfig.envPrefix) {
|
||||||
|
envPrefixes = Array.isArray(viteConfig.envPrefix)
|
||||||
|
? viteConfig.envPrefix
|
||||||
|
: [viteConfig.envPrefix];
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateEnv: Record<string, string> = {};
|
||||||
|
for (const key in fullEnv) {
|
||||||
|
// Ignore public env var
|
||||||
|
if (isValidIdentifierRe.test(key) && envPrefixes.every((prefix) => !key.startsWith(prefix))) {
|
||||||
|
if (typeof process.env[key] !== 'undefined') {
|
||||||
|
let value = process.env[key];
|
||||||
|
// Replacements are always strings, so try to convert to strings here first
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
value = `${value}`;
|
||||||
|
}
|
||||||
|
// Boolean values should be inlined to support `export const prerender`
|
||||||
|
// We already know that these are NOT sensitive values, so inlining is safe
|
||||||
|
if (value === '0' || value === '1' || value === 'true' || value === 'false') {
|
||||||
|
privateEnv[key] = value;
|
||||||
|
} else {
|
||||||
|
privateEnv[key] = `process.env.${key}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
privateEnv[key] = JSON.stringify(fullEnv[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return privateEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createEnvLoader = () => {
|
||||||
|
let privateEnv: Record<string, string> = {};
|
||||||
|
return {
|
||||||
|
load: (mode: string, config: AstroConfig) => {
|
||||||
|
const loaded = loadEnv(mode, config.vite.envDir ?? fileURLToPath(config.root), '');
|
||||||
|
privateEnv = getPrivateEnv(loaded, config);
|
||||||
|
return loaded;
|
||||||
|
},
|
||||||
|
getPrivateEnv: () => {
|
||||||
|
return privateEnv;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EnvLoader = ReturnType<typeof createEnvLoader>;
|
6
packages/astro/src/env/runtime.ts
vendored
6
packages/astro/src/env/runtime.ts
vendored
|
@ -4,14 +4,14 @@ import type { ValidationResultInvalid } from './validators.js';
|
||||||
export { validateEnvVariable, getEnvFieldType } from './validators.js';
|
export { validateEnvVariable, getEnvFieldType } from './validators.js';
|
||||||
|
|
||||||
export type GetEnv = (key: string) => string | undefined;
|
export type GetEnv = (key: string) => string | undefined;
|
||||||
type OnSetGetEnv = (reset: boolean) => void;
|
type OnSetGetEnv = () => void;
|
||||||
|
|
||||||
let _getEnv: GetEnv = (key) => process.env[key];
|
let _getEnv: GetEnv = (key) => process.env[key];
|
||||||
|
|
||||||
export function setGetEnv(fn: GetEnv, reset = false) {
|
export function setGetEnv(fn: GetEnv) {
|
||||||
_getEnv = fn;
|
_getEnv = fn;
|
||||||
|
|
||||||
_onSetGetEnv(reset);
|
_onSetGetEnv();
|
||||||
}
|
}
|
||||||
|
|
||||||
let _onSetGetEnv: OnSetGetEnv = () => {};
|
let _onSetGetEnv: OnSetGetEnv = () => {};
|
||||||
|
|
26
packages/astro/src/env/vite-plugin-env.ts
vendored
26
packages/astro/src/env/vite-plugin-env.ts
vendored
|
@ -1,6 +1,5 @@
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import type { Plugin } from 'vite';
|
||||||
import { type Plugin, loadEnv } from 'vite';
|
|
||||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||||
import type { AstroSettings } from '../types/astro.js';
|
import type { AstroSettings } from '../types/astro.js';
|
||||||
import {
|
import {
|
||||||
|
@ -11,28 +10,37 @@ import {
|
||||||
import { type InvalidVariable, invalidVariablesToError } from './errors.js';
|
import { type InvalidVariable, invalidVariablesToError } from './errors.js';
|
||||||
import type { EnvSchema } from './schema.js';
|
import type { EnvSchema } from './schema.js';
|
||||||
import { getEnvFieldType, validateEnvVariable } from './validators.js';
|
import { getEnvFieldType, validateEnvVariable } from './validators.js';
|
||||||
|
import type { EnvLoader } from './env-loader.js';
|
||||||
|
|
||||||
interface AstroEnvPluginParams {
|
interface AstroEnvPluginParams {
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
mode: string;
|
mode: string;
|
||||||
sync: boolean;
|
sync: boolean;
|
||||||
|
envLoader: EnvLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin {
|
export function astroEnv({ settings, mode, sync, envLoader }: AstroEnvPluginParams): Plugin {
|
||||||
const { schema, validateSecrets } = settings.config.env;
|
const { schema, validateSecrets } = settings.config.env;
|
||||||
|
let isDev: boolean;
|
||||||
|
|
||||||
let templates: { client: string; server: string; internal: string } | null = null;
|
let templates: { client: string; server: string; internal: string } | null = null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'astro-env-plugin',
|
name: 'astro-env-plugin',
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
|
config(_, { command }) {
|
||||||
|
isDev = command !== 'build';
|
||||||
|
},
|
||||||
buildStart() {
|
buildStart() {
|
||||||
const loadedEnv = loadEnv(mode, fileURLToPath(settings.config.root), '');
|
const loadedEnv = envLoader.load(mode, settings.config);
|
||||||
|
|
||||||
|
if (!isDev) {
|
||||||
for (const [key, value] of Object.entries(loadedEnv)) {
|
for (const [key, value] of Object.entries(loadedEnv)) {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
process.env[key] = value;
|
process.env[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const validatedVariables = validatePublicVariables({
|
const validatedVariables = validatePublicVariables({
|
||||||
schema,
|
schema,
|
||||||
|
@ -42,7 +50,7 @@ export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin
|
||||||
});
|
});
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
...getTemplates(schema, validatedVariables),
|
...getTemplates(schema, validatedVariables, isDev ? loadedEnv : null),
|
||||||
internal: `export const schema = ${JSON.stringify(schema)};`,
|
internal: `export const schema = ${JSON.stringify(schema)};`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -122,6 +130,7 @@ function validatePublicVariables({
|
||||||
function getTemplates(
|
function getTemplates(
|
||||||
schema: EnvSchema,
|
schema: EnvSchema,
|
||||||
validatedVariables: ReturnType<typeof validatePublicVariables>,
|
validatedVariables: ReturnType<typeof validatePublicVariables>,
|
||||||
|
loadedEnv: Record<string, string> | null,
|
||||||
) {
|
) {
|
||||||
let client = '';
|
let client = '';
|
||||||
let server = readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
|
let server = readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
|
||||||
|
@ -142,10 +151,15 @@ function getTemplates(
|
||||||
}
|
}
|
||||||
|
|
||||||
server += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
server += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
||||||
onSetGetEnv += `${key} = reset ? undefined : _internalGetSecret(${JSON.stringify(key)});\n`;
|
onSetGetEnv += `${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
server = server.replace('// @@ON_SET_GET_ENV@@', onSetGetEnv);
|
server = server.replace('// @@ON_SET_GET_ENV@@', onSetGetEnv);
|
||||||
|
if (loadedEnv) {
|
||||||
|
server = server.replace('// @@GET_ENV@@', `return (${JSON.stringify(loadedEnv)})[key];`);
|
||||||
|
} else {
|
||||||
|
server = server.replace('// @@GET_ENV@@', 'return _getEnv(key);');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
client,
|
client,
|
||||||
|
|
|
@ -191,7 +191,6 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
|
||||||
i18n: i18nManifest,
|
i18n: i18nManifest,
|
||||||
checkOrigin:
|
checkOrigin:
|
||||||
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
||||||
envGetSecretEnabled: false,
|
|
||||||
key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
|
key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
|
||||||
middleware() {
|
middleware() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,63 +1,14 @@
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import { transform } from 'esbuild';
|
import { transform } from 'esbuild';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import { loadEnv } from 'vite';
|
import type { EnvLoader } from '../env/env-loader.js';
|
||||||
import type { AstroSettings } from '../types/astro.js';
|
|
||||||
import type { AstroConfig } from '../types/public/config.js';
|
|
||||||
|
|
||||||
interface EnvPluginOptions {
|
interface EnvPluginOptions {
|
||||||
settings: AstroSettings;
|
envLoader: EnvLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match `import.meta.env` directly without trailing property access
|
// Match `import.meta.env` directly without trailing property access
|
||||||
const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
|
const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
|
||||||
// Match valid JS variable names (identifiers), which accepts most alphanumeric characters,
|
|
||||||
// except that the first character cannot be a number.
|
|
||||||
const isValidIdentifierRe = /^[_$a-zA-Z][\w$]*$/;
|
|
||||||
|
|
||||||
function getPrivateEnv(
|
|
||||||
viteConfig: vite.ResolvedConfig,
|
|
||||||
astroConfig: AstroConfig,
|
|
||||||
): Record<string, string> {
|
|
||||||
let envPrefixes: string[] = ['PUBLIC_'];
|
|
||||||
if (viteConfig.envPrefix) {
|
|
||||||
envPrefixes = Array.isArray(viteConfig.envPrefix)
|
|
||||||
? viteConfig.envPrefix
|
|
||||||
: [viteConfig.envPrefix];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads environment variables from `.env` files and `process.env`
|
|
||||||
const fullEnv = loadEnv(
|
|
||||||
viteConfig.mode,
|
|
||||||
viteConfig.envDir ?? fileURLToPath(astroConfig.root),
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
|
|
||||||
const privateEnv: Record<string, string> = {};
|
|
||||||
for (const key in fullEnv) {
|
|
||||||
// Ignore public env var
|
|
||||||
if (isValidIdentifierRe.test(key) && envPrefixes.every((prefix) => !key.startsWith(prefix))) {
|
|
||||||
if (typeof process.env[key] !== 'undefined') {
|
|
||||||
let value = process.env[key];
|
|
||||||
// Replacements are always strings, so try to convert to strings here first
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
value = `${value}`;
|
|
||||||
}
|
|
||||||
// Boolean values should be inlined to support `export const prerender`
|
|
||||||
// We already know that these are NOT sensitive values, so inlining is safe
|
|
||||||
if (value === '0' || value === '1' || value === 'true' || value === 'false') {
|
|
||||||
privateEnv[key] = value;
|
|
||||||
} else {
|
|
||||||
privateEnv[key] = `process.env.${key}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
privateEnv[key] = JSON.stringify(fullEnv[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return privateEnv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReferencedPrivateKeys(source: string, privateEnv: Record<string, any>): Set<string> {
|
function getReferencedPrivateKeys(source: string, privateEnv: Record<string, any>): Set<string> {
|
||||||
const references = new Set<string>();
|
const references = new Set<string>();
|
||||||
|
@ -114,13 +65,12 @@ async function replaceDefine(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plugin {
|
export default function envVitePlugin({ envLoader }: EnvPluginOptions): vite.Plugin {
|
||||||
let privateEnv: Record<string, string>;
|
let privateEnv: Record<string, string>;
|
||||||
let defaultDefines: Record<string, string>;
|
let defaultDefines: Record<string, string>;
|
||||||
let isDev: boolean;
|
let isDev: boolean;
|
||||||
let devImportMetaEnvPrepend: string;
|
let devImportMetaEnvPrepend: string;
|
||||||
let viteConfig: vite.ResolvedConfig;
|
let viteConfig: vite.ResolvedConfig;
|
||||||
const { config: astroConfig } = settings;
|
|
||||||
return {
|
return {
|
||||||
name: 'astro:vite-plugin-env',
|
name: 'astro:vite-plugin-env',
|
||||||
config(_, { command }) {
|
config(_, { command }) {
|
||||||
|
@ -152,7 +102,9 @@ export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plug
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find matches for *private* env and do our own replacement.
|
// Find matches for *private* env and do our own replacement.
|
||||||
privateEnv ??= getPrivateEnv(viteConfig, astroConfig);
|
// Env is retrieved before process.env is populated by astro:env
|
||||||
|
// so that import.meta.env is first replaced by values, not process.env
|
||||||
|
privateEnv ??= envLoader.getPrivateEnv();
|
||||||
|
|
||||||
// In dev, we can assign the private env vars to `import.meta.env` directly for performance
|
// In dev, we can assign the private env vars to `import.meta.env` directly for performance
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
|
|
@ -2,14 +2,23 @@
|
||||||
import { schema } from 'virtual:astro:env/internal';
|
import { schema } from 'virtual:astro:env/internal';
|
||||||
import {
|
import {
|
||||||
createInvalidVariablesError,
|
createInvalidVariablesError,
|
||||||
getEnv,
|
getEnv as _getEnv,
|
||||||
getEnvFieldType,
|
getEnvFieldType,
|
||||||
setOnSetGetEnv,
|
setOnSetGetEnv,
|
||||||
validateEnvVariable,
|
validateEnvVariable,
|
||||||
} from 'astro/env/runtime';
|
} from 'astro/env/runtime';
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
/** @returns {string} */
|
||||||
|
// used while generating the virtual module
|
||||||
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: `key` is used by the generated code
|
||||||
|
// biome-ignore lint/correctness/noUnusedVariables: `key` is used by the generated code
|
||||||
|
const getEnv = (key) => {
|
||||||
|
// @@GET_ENV@@
|
||||||
|
};
|
||||||
|
|
||||||
export const getSecret = (key) => {
|
export const getSecret = (key) => {
|
||||||
return getEnv(key);
|
return getEnv(key)
|
||||||
};
|
};
|
||||||
|
|
||||||
const _internalGetSecret = (key) => {
|
const _internalGetSecret = (key) => {
|
||||||
|
@ -25,9 +34,6 @@ const _internalGetSecret = (key) => {
|
||||||
throw createInvalidVariablesError(key, type, result);
|
throw createInvalidVariablesError(key, type, result);
|
||||||
};
|
};
|
||||||
|
|
||||||
// used while generating the virtual module
|
setOnSetGetEnv(() => {
|
||||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: `reset` is used by the generated code
|
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: `reset` is used by the generated code
|
|
||||||
setOnSetGetEnv((reset) => {
|
|
||||||
// @@ON_SET_GET_ENV@@
|
// @@ON_SET_GET_ENV@@
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue