@shopify/cli-hydrogen 7.1.0 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/commands/hydrogen/build-vite.js +131 -0
  2. package/dist/commands/hydrogen/build.js +6 -15
  3. package/dist/commands/hydrogen/check.js +1 -1
  4. package/dist/commands/hydrogen/codegen.js +3 -3
  5. package/dist/commands/hydrogen/debug/cpu.js +1 -1
  6. package/dist/commands/hydrogen/deploy.js +46 -31
  7. package/dist/commands/hydrogen/deploy.test.js +35 -49
  8. package/dist/commands/hydrogen/dev-vite.js +159 -0
  9. package/dist/commands/hydrogen/dev.js +11 -14
  10. package/dist/commands/hydrogen/env/list.js +1 -1
  11. package/dist/commands/hydrogen/env/pull.js +3 -3
  12. package/dist/commands/hydrogen/env/pull.test.js +2 -0
  13. package/dist/commands/hydrogen/env/push__unstable.js +190 -0
  14. package/dist/commands/hydrogen/env/push__unstable.test.js +383 -0
  15. package/dist/commands/hydrogen/generate/route.js +2 -2
  16. package/dist/commands/hydrogen/init.d.ts +69 -0
  17. package/dist/commands/hydrogen/init.js +5 -5
  18. package/dist/commands/hydrogen/init.test.js +2 -2
  19. package/dist/commands/hydrogen/link.js +2 -2
  20. package/dist/commands/hydrogen/list.js +1 -1
  21. package/dist/commands/hydrogen/login.js +2 -9
  22. package/dist/commands/hydrogen/logout.js +1 -1
  23. package/dist/commands/hydrogen/preview.js +15 -7
  24. package/dist/commands/hydrogen/setup/css.js +3 -3
  25. package/dist/commands/hydrogen/setup/markets.js +4 -4
  26. package/dist/commands/hydrogen/setup/vite.js +209 -0
  27. package/dist/commands/hydrogen/setup.js +8 -6
  28. package/dist/commands/hydrogen/unlink.js +1 -1
  29. package/dist/commands/hydrogen/upgrade.js +5 -3
  30. package/dist/generator-templates/assets/vite/package.json +15 -0
  31. package/dist/generator-templates/assets/vite/vite.config.js +13 -0
  32. package/dist/generator-templates/starter/CHANGELOG.md +49 -0
  33. package/dist/generator-templates/starter/app/components/Search.tsx +12 -7
  34. package/dist/generator-templates/starter/app/root.tsx +1 -2
  35. package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +8 -15
  36. package/dist/generator-templates/starter/package.json +9 -8
  37. package/dist/generator-templates/starter/public/.gitkeep +0 -0
  38. package/dist/lib/build.js +2 -1
  39. package/dist/lib/codegen.js +8 -3
  40. package/dist/lib/environment-variables.test.js +4 -2
  41. package/dist/lib/flags.js +149 -95
  42. package/dist/lib/graphql/admin/pull-variables.js +1 -0
  43. package/dist/lib/graphql/admin/pull-variables.test.js +7 -1
  44. package/dist/lib/graphql/admin/push-variables.js +35 -0
  45. package/dist/lib/log.js +1 -0
  46. package/dist/lib/mini-oxygen/common.js +2 -1
  47. package/dist/lib/mini-oxygen/node.js +2 -2
  48. package/dist/lib/mini-oxygen/workerd-inspector.js +1 -1
  49. package/dist/lib/mini-oxygen/workerd.js +29 -17
  50. package/dist/lib/onboarding/common.js +0 -3
  51. package/dist/lib/onboarding/local.js +4 -1
  52. package/dist/lib/onboarding/remote.js +16 -11
  53. package/dist/lib/remix-config.js +1 -1
  54. package/dist/lib/request-events.js +3 -3
  55. package/dist/lib/setups/css/assets.js +7 -2
  56. package/dist/lib/template-diff.js +26 -11
  57. package/dist/lib/template-downloader.js +11 -2
  58. package/dist/lib/vite/hydrogen-middleware.js +82 -0
  59. package/dist/lib/vite/mini-oxygen.js +152 -0
  60. package/dist/lib/vite/plugins.d.ts +27 -0
  61. package/dist/lib/vite/plugins.js +139 -0
  62. package/dist/lib/vite/shared.js +10 -0
  63. package/dist/lib/vite/utils.js +55 -0
  64. package/dist/lib/vite/worker-entry.js +1518 -0
  65. package/dist/lib/vite-config.js +45 -0
  66. package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +4 -2
  67. package/dist/virtual-routes/routes/index.jsx +5 -5
  68. package/dist/virtual-routes/routes/subrequest-profiler.jsx +1 -1
  69. package/dist/virtual-routes/virtual-root.jsx +1 -1
  70. package/oclif.manifest.json +1127 -494
  71. package/package.json +36 -11
  72. /package/dist/generator-templates/starter/{public → app/assets}/favicon.svg +0 -0
@@ -0,0 +1,159 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { muteDevLogs, enhanceH2Logs } from '../../lib/log.js';
4
+ import { commonFlags, overrideFlag, flagsToCamelObject } from '../../lib/flags.js';
5
+ import Command from '@shopify/cli-kit/node/base-command';
6
+ import colors from '@shopify/cli-kit/node/colors';
7
+ import { renderInfo } from '@shopify/cli-kit/node/ui';
8
+ import { AbortError } from '@shopify/cli-kit/node/error';
9
+ import { Flags } from '@oclif/core';
10
+ import { spawnCodegenProcess } from '../../lib/codegen.js';
11
+ import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
12
+ import { getConfig } from '../../lib/shopify-config.js';
13
+ import { checkRemixVersions } from '../../lib/remix-version-check.js';
14
+ import { displayDevUpgradeNotice } from './upgrade.js';
15
+ import { prepareDiffDirectory } from '../../lib/template-diff.js';
16
+ import { setH2OPluginContext } from '../../lib/vite/shared.js';
17
+ import { getGraphiQLUrl } from '../../lib/graphiql-url.js';
18
+ import { getDebugBannerLine } from '../../lib/mini-oxygen/workerd.js';
19
+
20
+ class DevVite extends Command {
21
+ static description = "Runs Hydrogen storefront in an Oxygen worker for development.";
22
+ static flags = {
23
+ ...commonFlags.path,
24
+ ...commonFlags.entry,
25
+ ...overrideFlag(commonFlags.port, {
26
+ port: { default: void 0, required: false }
27
+ }),
28
+ ...commonFlags.codegen,
29
+ "disable-virtual-routes": Flags.boolean({
30
+ description: "Disable rendering fallback routes when a route file doesn't exist.",
31
+ env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES",
32
+ default: false
33
+ }),
34
+ ...commonFlags.debug,
35
+ ...commonFlags.inspectorPort,
36
+ host: Flags.boolean({
37
+ description: "Expose the server to the network",
38
+ default: false,
39
+ required: false
40
+ }),
41
+ ...commonFlags.envBranch,
42
+ "disable-version-check": Flags.boolean({
43
+ description: "Skip the version check when running `hydrogen dev`",
44
+ default: false,
45
+ required: false
46
+ }),
47
+ ...commonFlags.diff
48
+ };
49
+ async run() {
50
+ const { flags } = await this.parse(DevVite);
51
+ let directory = flags.path ? path.resolve(flags.path) : process.cwd();
52
+ if (flags.diff) {
53
+ directory = await prepareDiffDirectory(directory, true);
54
+ }
55
+ await runDev({
56
+ ...flagsToCamelObject(flags),
57
+ path: directory,
58
+ isLocalDev: flags.diff
59
+ });
60
+ }
61
+ }
62
+ async function runDev({
63
+ entry: ssrEntry,
64
+ port: appPort,
65
+ path: appPath,
66
+ host,
67
+ codegen: useCodegen = false,
68
+ codegenConfigPath,
69
+ disableVirtualRoutes,
70
+ envBranch,
71
+ debug = false,
72
+ disableVersionCheck = false,
73
+ inspectorPort,
74
+ isLocalDev = false
75
+ }) {
76
+ if (!process.env.NODE_ENV)
77
+ process.env.NODE_ENV = "development";
78
+ muteDevLogs();
79
+ const root = appPath ?? process.cwd();
80
+ const envPromise = getConfig(root).then(({ shop, storefront }) => {
81
+ const fetchRemote = !!shop && !!storefront?.id;
82
+ return getAllEnvironmentVariables({ root, fetchRemote, envBranch });
83
+ });
84
+ const vite = await import('vite');
85
+ const fs = isLocalDev ? { allow: [root, fileURLToPath(new URL("../../../../", import.meta.url))] } : void 0;
86
+ const viteServer = await vite.createServer({
87
+ root,
88
+ server: { fs, host: host ? true : void 0 },
89
+ ...setH2OPluginContext({
90
+ cliOptions: {
91
+ debug,
92
+ ssrEntry,
93
+ envPromise,
94
+ inspectorPort,
95
+ disableVirtualRoutes
96
+ }
97
+ })
98
+ });
99
+ process.once("SIGTERM", async () => {
100
+ try {
101
+ await viteServer.close();
102
+ } finally {
103
+ process.exit();
104
+ }
105
+ });
106
+ if (!viteServer.config.plugins.find((plugin) => plugin.name === "hydrogen:main")) {
107
+ await viteServer.close();
108
+ throw new AbortError(
109
+ "Hydrogen plugin not found.",
110
+ "Add `hydrogen()` plugin to your Vite config."
111
+ );
112
+ }
113
+ const codegenProcess = useCodegen ? spawnCodegenProcess({
114
+ rootDirectory: root,
115
+ configFilePath: codegenConfigPath
116
+ }) : void 0;
117
+ process.on("unhandledRejection", (err) => {
118
+ console.log("Unhandled Rejection: ", err);
119
+ });
120
+ const publicPort = appPort ?? viteServer.config.server.port ?? 3e3;
121
+ await viteServer.listen(publicPort);
122
+ const publicUrl = new URL(
123
+ viteServer.resolvedUrls.local[0] ?? viteServer.resolvedUrls.network[0]
124
+ );
125
+ enhanceH2Logs({ rootDirectory: root, host: publicUrl.toString() });
126
+ await envPromise;
127
+ console.log("");
128
+ viteServer.printUrls();
129
+ viteServer.bindCLIShortcuts({ print: true });
130
+ console.log("\n");
131
+ const infoLines = [];
132
+ if (!disableVirtualRoutes) {
133
+ infoLines.push(
134
+ `${colors.dim("View GraphiQL API browser:")} ${getGraphiQLUrl({
135
+ host: publicUrl.origin
136
+ })}`,
137
+ `
138
+ ${colors.dim("View server network requests:")} ${publicUrl.origin}/subrequest-profiler`
139
+ );
140
+ }
141
+ if (debug) {
142
+ infoLines.push({ warn: getDebugBannerLine(inspectorPort) });
143
+ }
144
+ if (infoLines.length > 0) {
145
+ renderInfo({ body: infoLines });
146
+ }
147
+ checkRemixVersions();
148
+ if (!disableVersionCheck) {
149
+ displayDevUpgradeNotice({ targetPath: root });
150
+ }
151
+ return {
152
+ async close() {
153
+ codegenProcess?.kill(0);
154
+ await viteServer.close();
155
+ }
156
+ };
157
+ }
158
+
159
+ export { DevVite as default, runDev };
@@ -7,7 +7,7 @@ import colors from '@shopify/cli-kit/node/colors';
7
7
  import { copyPublicFiles } from './build.js';
8
8
  import { getProjectPaths, assertOxygenChecks, handleRemixImportFail, getRemixConfig } from '../../lib/remix-config.js';
9
9
  import { muteDevLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
10
- import { commonFlags, deprecated, overrideFlag, flagsToCamelObject } from '../../lib/flags.js';
10
+ import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
11
11
  import Command from '@shopify/cli-kit/node/base-command';
12
12
  import { Flags } from '@oclif/core';
13
13
  import { buildAssetsUrl, startMiniOxygen } from '../../lib/mini-oxygen/index.js';
@@ -27,29 +27,26 @@ const LOG_REBUILT = "\u{1F680} Rebuilt";
27
27
  class Dev extends Command {
28
28
  static description = "Runs Hydrogen storefront in an Oxygen worker for development.";
29
29
  static flags = {
30
- path: commonFlags.path,
31
- port: commonFlags.port,
30
+ ...commonFlags.path,
31
+ ...commonFlags.port,
32
32
  worker: deprecated("--worker", { isBoolean: true }),
33
- "legacy-runtime": commonFlags.legacyRuntime,
34
- codegen: overrideFlag(commonFlags.codegen, {
35
- description: commonFlags.codegen.description + " It updates the types on file save."
36
- }),
37
- "codegen-config-path": commonFlags.codegenConfigPath,
38
- sourcemap: commonFlags.sourcemap,
33
+ ...commonFlags.legacyRuntime,
34
+ ...commonFlags.codegen,
35
+ ...commonFlags.sourcemap,
39
36
  "disable-virtual-routes": Flags.boolean({
40
37
  description: "Disable rendering fallback routes when a route file doesn't exist.",
41
38
  env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES",
42
39
  default: false
43
40
  }),
44
- debug: commonFlags.debug,
45
- "inspector-port": commonFlags.inspectorPort,
46
- ["env-branch"]: commonFlags.envBranch,
47
- ["disable-version-check"]: Flags.boolean({
41
+ ...commonFlags.debug,
42
+ ...commonFlags.inspectorPort,
43
+ ...commonFlags.envBranch,
44
+ "disable-version-check": Flags.boolean({
48
45
  description: "Skip the version check when running `hydrogen dev`",
49
46
  default: false,
50
47
  required: false
51
48
  }),
52
- diff: commonFlags.diff
49
+ ...commonFlags.diff
53
50
  };
54
51
  async run() {
55
52
  const { flags } = await this.parse(Dev);
@@ -13,7 +13,7 @@ import { getCliCommand } from '../../../lib/shell.js';
13
13
  class EnvList extends Command {
14
14
  static description = "List the environments on your linked Hydrogen storefront.";
15
15
  static flags = {
16
- path: commonFlags.path
16
+ ...commonFlags.path
17
17
  };
18
18
  async run() {
19
19
  const { flags } = await this.parse(EnvList);
@@ -16,9 +16,9 @@ import { getStorefrontEnvVariables } from '../../../lib/graphql/admin/pull-varia
16
16
  class EnvPull extends Command {
17
17
  static description = "Populate your .env with variables from your Hydrogen storefront.";
18
18
  static flags = {
19
- ["env-branch"]: commonFlags.envBranch,
20
- path: commonFlags.path,
21
- force: commonFlags.force
19
+ ...commonFlags.envBranch,
20
+ ...commonFlags.path,
21
+ ...commonFlags.force
22
22
  };
23
23
  async run() {
24
24
  const { flags } = await this.parse(EnvPull);
@@ -46,12 +46,14 @@ describe("pullVariables", () => {
46
46
  id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/1",
47
47
  key: "PUBLIC_API_TOKEN",
48
48
  value: "abc123",
49
+ readOnly: true,
49
50
  isSecret: false
50
51
  },
51
52
  {
52
53
  id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/2",
53
54
  key: "PRIVATE_API_TOKEN",
54
55
  value: "",
56
+ readOnly: true,
55
57
  isSecret: true
56
58
  }
57
59
  ]
@@ -0,0 +1,190 @@
1
+ import Command from '@shopify/cli-kit/node/base-command';
2
+ import { diffLines } from 'diff';
3
+ import { Flags } from '@oclif/core';
4
+ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
5
+ import { login } from '../../../lib/auth.js';
6
+ import { getCliCommand } from '../../../lib/shell.js';
7
+ import { resolvePath } from '@shopify/cli-kit/node/path';
8
+ import { renderConfirmationPrompt, renderSelectPrompt, renderInfo, renderSuccess } from '@shopify/cli-kit/node/ui';
9
+ import { outputContent, outputToken, outputWarn } from '@shopify/cli-kit/node/output';
10
+ import { renderMissingLink } from '../../../lib/render-errors.js';
11
+ import { getStorefrontEnvironments } from '../../../lib/graphql/admin/list-environments.js';
12
+ import { linkStorefront } from '../link.js';
13
+ import { getStorefrontEnvVariables } from '../../../lib/graphql/admin/pull-variables.js';
14
+ import { pushStorefrontEnvVariables } from '../../../lib/graphql/admin/push-variables.js';
15
+ import { AbortError } from '@shopify/cli-kit/node/error';
16
+ import { readAndParseDotEnv } from '@shopify/cli-kit/node/dot-env';
17
+
18
+ class EnvPush extends Command {
19
+ static description = "Push environment variables from the local .env file to your linked Hydrogen storefront.";
20
+ static hidden = true;
21
+ static flags = {
22
+ ...commonFlags.env,
23
+ "env-file": Flags.string({
24
+ description: "Specify the environment variable file name. Default value is '.env'.",
25
+ env: "SHOPIFY_HYDROGEN_ENVIRONMENT_FILENAME"
26
+ }),
27
+ ...commonFlags.path
28
+ };
29
+ async run() {
30
+ const { flags } = await this.parse(EnvPush);
31
+ await runEnvPush({ ...flagsToCamelObject(flags) });
32
+ }
33
+ }
34
+ async function runEnvPush({
35
+ env: environmentName,
36
+ envFile = ".env",
37
+ path = process.cwd()
38
+ }) {
39
+ let validatedEnvironment = {};
40
+ const dotEnvPath = resolvePath(path, envFile);
41
+ const { variables: localVariables } = await readAndParseDotEnv(dotEnvPath);
42
+ const [{ session, config }, cliCommand] = await Promise.all([
43
+ login(path),
44
+ getCliCommand()
45
+ ]);
46
+ if (!config.storefront?.id) {
47
+ renderMissingLink({ session, cliCommand });
48
+ const runLink = await renderConfirmationPrompt({
49
+ message: outputContent`Run ${outputToken.genericShellCommand(
50
+ `${cliCommand} link`
51
+ )}?`.value
52
+ });
53
+ if (!runLink)
54
+ return;
55
+ config.storefront = await linkStorefront(path, session, config, {
56
+ cliCommand
57
+ });
58
+ }
59
+ if (!config.storefront?.id)
60
+ return;
61
+ const { environments: environmentsData } = await getStorefrontEnvironments(session, config.storefront.id) ?? {};
62
+ if (!environmentsData) {
63
+ throw new AbortError("Failed to fetch environments");
64
+ }
65
+ const environments = [
66
+ ...environmentsData.filter((environment) => environment.type === "PREVIEW"),
67
+ ...environmentsData.filter((environment) => environment.type === "CUSTOM"),
68
+ ...environmentsData.filter(
69
+ (environment) => environment.type === "PRODUCTION"
70
+ )
71
+ ];
72
+ if (environments.length === 0) {
73
+ throw new AbortError("No environments found");
74
+ }
75
+ if (environmentName) {
76
+ const matchedEnvironments = environments.filter(
77
+ ({ name }) => name === environmentName
78
+ );
79
+ if (matchedEnvironments.length === 0) {
80
+ throw new AbortError(
81
+ "Environment not found",
82
+ `We could not find an environment matching the name '${environmentName}'.`
83
+ );
84
+ } else if (matchedEnvironments.length === 1) {
85
+ const { id, name, branch, type } = matchedEnvironments[0] ?? {};
86
+ validatedEnvironment = { id, name, branch, type };
87
+ } else {
88
+ const selection = await renderSelectPrompt({
89
+ message: `There were multiple environments found with the name ${environmentName}:`,
90
+ choices: [
91
+ ...matchedEnvironments.map(({ id: id2, name: name2, branch: branch2, type: type2, url }) => ({
92
+ label: `${name2} (${branch2}) ${type2} ${url}`,
93
+ value: id2
94
+ }))
95
+ ]
96
+ });
97
+ const { id, name, branch, type } = matchedEnvironments.find(({ id: id2 }) => id2 === selection) ?? {};
98
+ validatedEnvironment = { id, name, branch, type };
99
+ }
100
+ } else {
101
+ const choices = [
102
+ ...environments.map(({ id: id2, name: name2, branch: branch2 }) => ({
103
+ label: branch2 ? `${name2} (${branch2})` : name2,
104
+ value: id2
105
+ }))
106
+ ];
107
+ const pushToBranchSelection = await renderSelectPrompt({
108
+ message: "Select a set of environment variables to overwrite:",
109
+ choices
110
+ });
111
+ const { id, name, branch, type } = environments.find(({ id: id2 }) => id2 === pushToBranchSelection) ?? {};
112
+ validatedEnvironment = { id, name, branch, type };
113
+ }
114
+ const { environmentVariables = [] } = await getStorefrontEnvVariables(
115
+ session,
116
+ config.storefront.id,
117
+ validatedEnvironment.branch ?? void 0
118
+ ) ?? {};
119
+ const remoteVars = environmentVariables.filter(
120
+ ({ isSecret, readOnly }) => !isSecret && !readOnly
121
+ );
122
+ const comparableRemoteVars = remoteVars.sort((a, b) => a.key.localeCompare(b.key)).map(({ key, value }) => `${key}=${value}`).join("\n") + "\n";
123
+ const compareableLocalVars = Object.keys(localVariables).sort((a, b) => a.localeCompare(b)).reduce((acc, key) => {
124
+ const { isSecret, readOnly } = environmentVariables.find((variable) => variable.key === key) ?? {};
125
+ if (isSecret || readOnly)
126
+ return acc;
127
+ return [...acc, `${key}=${localVariables[key]}`];
128
+ }, []).join("\n") + "\n";
129
+ if (!validatedEnvironment.name)
130
+ throw new AbortError("Missing environment name");
131
+ const remoteReadOnlyOrSecrets = environmentVariables.reduce(
132
+ (acc, { isSecret, readOnly, key }) => {
133
+ if (!isSecret && !readOnly)
134
+ return acc;
135
+ const localVar = localVariables[key];
136
+ const remoteVar = environmentVariables.find(
137
+ (variable) => variable.key === key
138
+ );
139
+ if (localVar === remoteVar?.value)
140
+ return acc;
141
+ return [...acc, key];
142
+ },
143
+ []
144
+ );
145
+ if (remoteReadOnlyOrSecrets.length) {
146
+ outputWarn(
147
+ `Variables that are read only or contain secret values cannot be pushed from the CLI: ${remoteReadOnlyOrSecrets.join(
148
+ ", "
149
+ )}.
150
+ `
151
+ );
152
+ }
153
+ if (compareableLocalVars === comparableRemoteVars) {
154
+ renderInfo({
155
+ body: "No changes to your environment variables."
156
+ });
157
+ return;
158
+ } else {
159
+ const diff = diffLines(comparableRemoteVars, compareableLocalVars);
160
+ const confirmPush = await renderConfirmationPrompt({
161
+ confirmationMessage: "Yes, confirm changes",
162
+ cancellationMessage: "No, make changes later",
163
+ message: outputContent`We'll make the following changes to your environment variables for ${validatedEnvironment.name}:
164
+
165
+ ${outputToken.linesDiff(diff)}
166
+ Continue?`.value
167
+ });
168
+ if (!confirmPush)
169
+ return;
170
+ }
171
+ if (!validatedEnvironment.id)
172
+ throw new AbortError("Missing environment ID");
173
+ const { userErrors } = await pushStorefrontEnvVariables(
174
+ session,
175
+ config.storefront.id,
176
+ validatedEnvironment.id,
177
+ Object.entries(localVariables).map(([key, value]) => ({ key, value }))
178
+ );
179
+ if (userErrors.length) {
180
+ throw new AbortError(
181
+ "Failed to upload and save environment variables.",
182
+ userErrors[0]?.message
183
+ );
184
+ }
185
+ renderSuccess({
186
+ body: `Environment variables push to ${validatedEnvironment.name ?? "Preview"} was successful.`
187
+ });
188
+ }
189
+
190
+ export { EnvPush as default, runEnvPush };