@shopify/cli-hydrogen 6.1.1 → 7.0.1
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.
- package/dist/commands/hydrogen/build.js +40 -78
- package/dist/commands/hydrogen/codegen.js +8 -3
- package/dist/commands/hydrogen/deploy.js +311 -21
- package/dist/commands/hydrogen/deploy.test.js +410 -0
- package/dist/commands/hydrogen/dev.js +30 -15
- package/dist/commands/hydrogen/init.js +1 -1
- package/dist/commands/hydrogen/init.test.js +155 -53
- package/dist/commands/hydrogen/link.js +5 -21
- package/dist/commands/hydrogen/link.test.js +10 -10
- package/dist/commands/hydrogen/preview.js +7 -6
- package/dist/commands/hydrogen/setup.js +17 -23
- package/dist/commands/hydrogen/setup.test.js +0 -1
- package/dist/commands/hydrogen/upgrade.js +15 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
- package/dist/generator-templates/starter/CHANGELOG.md +66 -0
- package/dist/generator-templates/starter/README.md +23 -0
- package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
- package/dist/generator-templates/starter/app/components/Layout.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
- package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
- package/dist/generator-templates/starter/app/lib/session.ts +67 -0
- package/dist/generator-templates/starter/app/root.tsx +11 -45
- package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
- package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
- package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
- package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
- package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
- package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
- package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
- package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
- package/dist/generator-templates/starter/package.json +11 -11
- package/dist/generator-templates/starter/remix.config.js +4 -0
- package/dist/generator-templates/starter/remix.env.d.ts +8 -11
- package/dist/generator-templates/starter/server.ts +24 -167
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
- package/dist/hooks/init.js +4 -4
- package/dist/lib/auth.js +5 -10
- package/dist/lib/build.js +6 -1
- package/dist/lib/bundle/analyzer.js +36 -26
- package/dist/lib/codegen.js +59 -26
- package/dist/lib/defer.js +12 -0
- package/dist/lib/file.js +55 -3
- package/dist/lib/flags.js +15 -8
- package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
- package/dist/lib/graphiql-url.js +3 -0
- package/dist/lib/graphql/admin/client.test.js +2 -2
- package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
- package/dist/lib/log.js +31 -14
- package/dist/lib/mini-oxygen/assets.js +17 -1
- package/dist/lib/mini-oxygen/index.js +4 -5
- package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
- package/dist/lib/mini-oxygen/node.js +4 -2
- package/dist/lib/mini-oxygen/workerd-inspector-logs.js +2 -2
- package/dist/lib/mini-oxygen/workerd.js +27 -10
- package/dist/lib/missing-routes.js +6 -3
- package/dist/lib/onboarding/common.js +44 -12
- package/dist/lib/onboarding/local.js +26 -18
- package/dist/lib/onboarding/remote.js +50 -29
- package/dist/lib/request-events.js +65 -31
- package/dist/lib/setups/css/assets.js +1 -46
- package/dist/lib/setups/css/css-modules.js +3 -2
- package/dist/lib/setups/css/postcss.js +4 -2
- package/dist/lib/setups/css/tailwind.js +4 -2
- package/dist/lib/setups/css/vanilla-extract.js +3 -2
- package/dist/lib/setups/i18n/replacers.test.js +54 -38
- package/dist/lib/shell.js +1 -1
- package/dist/lib/template-diff.js +99 -0
- package/dist/lib/template-downloader.js +3 -2
- package/dist/lib/transpile/project.js +1 -1
- package/dist/virtual-routes/assets/debug-network.css +592 -0
- package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
- package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
- package/dist/virtual-routes/components/IconClose.jsx +38 -0
- package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
- package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
- package/dist/virtual-routes/components/RequestTable.jsx +92 -0
- package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
- package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
- package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
- package/oclif.manifest.json +54 -61
- package/package.json +15 -12
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
- package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
- package/dist/virtual-routes/routes/debug-network.jsx +0 -289
- /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
|
-
import { outputInfo,
|
|
3
|
+
import { outputInfo, outputContent, outputToken, outputWarn } from '@shopify/cli-kit/node/output';
|
|
4
4
|
import { rmdir, fileSize, glob, readFile, writeFile, fileExists, copyFile } from '@shopify/cli-kit/node/fs';
|
|
5
|
-
import { resolvePath,
|
|
5
|
+
import { resolvePath, relativePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
7
7
|
import colors from '@shopify/cli-kit/node/colors';
|
|
8
8
|
import { getProjectPaths, getRemixConfig, handleRemixImportFail, assertOxygenChecks } from '../../lib/remix-config.js';
|
|
9
|
-
import { commonFlags,
|
|
9
|
+
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
10
10
|
import { checkLockfileStatus } from '../../lib/check-lockfile.js';
|
|
11
11
|
import { findMissingRoutes } from '../../lib/missing-routes.js';
|
|
12
12
|
import { muteRemixLogs, createRemixLogger } from '../../lib/log.js';
|
|
13
13
|
import { codegen } from '../../lib/codegen.js';
|
|
14
|
-
import {
|
|
15
|
-
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
14
|
+
import { buildBundleAnalysis, getBundleAnalysisSummary } from '../../lib/bundle/analyzer.js';
|
|
16
15
|
import { isCI } from '../../lib/is-ci.js';
|
|
16
|
+
import { prepareDiffDirectory, copyDiffBuild } from '../../lib/template-diff.js';
|
|
17
17
|
|
|
18
18
|
const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
|
|
19
|
-
const
|
|
19
|
+
const WORKER_BUILD_SIZE_LIMIT = 5;
|
|
20
20
|
class Build extends Command {
|
|
21
21
|
static description = "Builds a Hydrogen storefront for production.";
|
|
22
22
|
static flags = {
|
|
@@ -44,18 +44,24 @@ class Build extends Command {
|
|
|
44
44
|
}),
|
|
45
45
|
codegen: commonFlags.codegen,
|
|
46
46
|
"codegen-config-path": commonFlags.codegenConfigPath,
|
|
47
|
-
|
|
48
|
-
entry: deprecated("--entry")(),
|
|
49
|
-
target: deprecated("--target")()
|
|
47
|
+
diff: commonFlags.diff
|
|
50
48
|
};
|
|
51
49
|
async run() {
|
|
52
50
|
const { flags } = await this.parse(Build);
|
|
53
|
-
const
|
|
51
|
+
const originalDirectory = flags.path ? resolvePath(flags.path) : process.cwd();
|
|
52
|
+
let directory = originalDirectory;
|
|
53
|
+
if (flags.diff) {
|
|
54
|
+
directory = await prepareDiffDirectory(originalDirectory, false);
|
|
55
|
+
}
|
|
54
56
|
await runBuild({
|
|
55
57
|
...flagsToCamelObject(flags),
|
|
56
58
|
useCodegen: flags.codegen,
|
|
57
59
|
directory
|
|
58
60
|
});
|
|
61
|
+
if (flags.diff) {
|
|
62
|
+
await copyDiffBuild(directory, originalDirectory);
|
|
63
|
+
}
|
|
64
|
+
process.exit(0);
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
async function runBuild({
|
|
@@ -104,28 +110,37 @@ async function runBuild({
|
|
|
104
110
|
fileWatchCache: createFileWatchCache()
|
|
105
111
|
}).catch((thrown) => {
|
|
106
112
|
logThrown(thrown);
|
|
107
|
-
process.
|
|
113
|
+
if (process.env.SHOPIFY_UNIT_TEST) {
|
|
114
|
+
throw thrown;
|
|
115
|
+
} else {
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
108
118
|
}),
|
|
109
119
|
useCodegen && codegen({ ...remixConfig, configFilePath: codegenConfigPath })
|
|
110
120
|
]);
|
|
111
121
|
if (process.env.NODE_ENV !== "development") {
|
|
112
122
|
console.timeEnd(LOG_WORKER_BUILT);
|
|
123
|
+
const bundleAnalysisPath = await buildBundleAnalysis(buildPath);
|
|
113
124
|
const sizeMB = await fileSize(buildPathWorkerFile) / (1024 * 1024);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
root,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
const formattedSize = colors.yellow(sizeMB.toFixed(2) + " MB");
|
|
126
|
+
outputInfo(
|
|
127
|
+
outputContent` ${colors.dim(
|
|
128
|
+
relativePath(root, buildPathWorkerFile)
|
|
129
|
+
)} ${bundleAnalysisPath ? outputToken.link(formattedSize, bundleAnalysisPath) : formattedSize}\n`
|
|
130
|
+
);
|
|
131
|
+
if (bundleStats && bundleAnalysisPath) {
|
|
132
|
+
outputInfo(
|
|
133
|
+
outputContent`${await getBundleAnalysisSummary(buildPathWorkerFile) || "\n"}\n │\n └─── ${outputToken.link(
|
|
134
|
+
"Complete analysis: " + bundleAnalysisPath,
|
|
135
|
+
bundleAnalysisPath
|
|
136
|
+
)}\n\n`
|
|
122
137
|
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
}
|
|
139
|
+
if (sizeMB >= WORKER_BUILD_SIZE_LIMIT) {
|
|
140
|
+
outputWarn(
|
|
141
|
+
`\u{1F6A8} Smaller worker bundles are faster to deploy and run.${remixConfig.serverMinify ? "" : "\n Minify your bundle by adding `serverMinify: true` to remix.config.js."}
|
|
142
|
+
Learn more about optimizing your worker bundle file: https://h2o.fyi/debugging/bundle-size
|
|
143
|
+
`
|
|
129
144
|
);
|
|
130
145
|
}
|
|
131
146
|
}
|
|
@@ -145,9 +160,6 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
|
|
|
145
160
|
if (process.env.NODE_ENV !== "development") {
|
|
146
161
|
await cleanClientSourcemaps(buildPathClient);
|
|
147
162
|
}
|
|
148
|
-
if (!process.env.SHOPIFY_UNIT_TEST && !assetPath) {
|
|
149
|
-
process.exit(0);
|
|
150
|
-
}
|
|
151
163
|
}
|
|
152
164
|
async function cleanClientSourcemaps(buildPathClient) {
|
|
153
165
|
const bundleFiles = await glob(joinPath(buildPathClient, "**/*.js"));
|
|
@@ -161,56 +173,6 @@ async function cleanClientSourcemaps(buildPathClient) {
|
|
|
161
173
|
})
|
|
162
174
|
);
|
|
163
175
|
}
|
|
164
|
-
async function writeBundleAnalysis(buildPath, root, buildPathWorkerFile, sizeMB, bundleStats, remixConfig) {
|
|
165
|
-
const bundleAnalysisPath = await buildBundleAnalysis(buildPath);
|
|
166
|
-
outputInfo(
|
|
167
|
-
outputContent` ${colors.dim(
|
|
168
|
-
relativePath(root, buildPathWorkerFile)
|
|
169
|
-
)} ${outputToken.link(
|
|
170
|
-
colors.yellow(sizeMB.toFixed(2) + " MB"),
|
|
171
|
-
bundleAnalysisPath
|
|
172
|
-
)}\n`
|
|
173
|
-
);
|
|
174
|
-
if (bundleStats && sizeMB < MAX_WORKER_BUNDLE_SIZE) {
|
|
175
|
-
outputInfo(
|
|
176
|
-
outputContent`${await getBundleAnalysisSummary(buildPathWorkerFile) || "\n"}\n │\n └─── ${outputToken.link(
|
|
177
|
-
"Complete analysis: " + bundleAnalysisPath,
|
|
178
|
-
bundleAnalysisPath
|
|
179
|
-
)}\n\n`
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
if (sizeMB >= MAX_WORKER_BUNDLE_SIZE) {
|
|
183
|
-
throw new AbortError(
|
|
184
|
-
"\u{1F6A8} Worker bundle exceeds 10 MB! Oxygen has a maximum worker bundle size of 10 MB.",
|
|
185
|
-
outputContent`See the bundle analysis for a breakdown of what is contributing to the bundle size:\n${outputToken.link(
|
|
186
|
-
bundleAnalysisPath,
|
|
187
|
-
bundleAnalysisPath
|
|
188
|
-
)}`
|
|
189
|
-
);
|
|
190
|
-
} else if (sizeMB >= 5) {
|
|
191
|
-
outputWarn(
|
|
192
|
-
`\u{1F6A8} Worker bundle exceeds 5 MB! This can delay your worker response.${remixConfig.serverMinify ? "" : " Minify your bundle by adding `serverMinify: true` to remix.config.js."}
|
|
193
|
-
`
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async function writeSimpleBuildStatus(root, buildPathWorkerFile, sizeMB, remixConfig) {
|
|
198
|
-
outputInfo(
|
|
199
|
-
outputContent` ${colors.dim(
|
|
200
|
-
relativePath(root, buildPathWorkerFile)
|
|
201
|
-
)} ${colors.yellow(sizeMB.toFixed(2) + " MB")}\n`
|
|
202
|
-
);
|
|
203
|
-
if (sizeMB >= MAX_WORKER_BUNDLE_SIZE) {
|
|
204
|
-
throw new AbortError(
|
|
205
|
-
"\u{1F6A8} Worker bundle exceeds 10 MB! Oxygen has a maximum worker bundle size of 10 MB."
|
|
206
|
-
);
|
|
207
|
-
} else if (sizeMB >= 5) {
|
|
208
|
-
outputWarn(
|
|
209
|
-
`\u{1F6A8} Worker bundle exceeds 5 MB! This can delay your worker response.${remixConfig.serverMinify ? "" : " Minify your bundle by adding `serverMinify: true` to remix.config.js."}
|
|
210
|
-
`
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
176
|
async function copyPublicFiles(publicPath, buildPathClient) {
|
|
215
177
|
if (!await fileExists(publicPath)) {
|
|
216
178
|
return;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
3
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
4
|
+
import colors from '@shopify/cli-kit/node/colors';
|
|
4
5
|
import { Flags } from '@oclif/core';
|
|
5
6
|
import { getProjectPaths, getRemixConfig } from '../../lib/remix-config.js';
|
|
6
7
|
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
@@ -24,8 +25,6 @@ class Codegen extends Command {
|
|
|
24
25
|
default: false
|
|
25
26
|
})
|
|
26
27
|
};
|
|
27
|
-
static aliases = ["codegen-unstable"];
|
|
28
|
-
static deprecateAliases = true;
|
|
29
28
|
async run() {
|
|
30
29
|
const { flags } = await this.parse(Codegen);
|
|
31
30
|
const directory = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
@@ -53,7 +52,13 @@ async function runCodegen({
|
|
|
53
52
|
if (!watch) {
|
|
54
53
|
renderSuccess({
|
|
55
54
|
headline: "Generated types for GraphQL:",
|
|
56
|
-
body:
|
|
55
|
+
body: {
|
|
56
|
+
list: {
|
|
57
|
+
items: Object.entries(generatedFiles).map(
|
|
58
|
+
([key, value]) => key + "\n" + value.map((item) => colors.dim(`- ${item}`)).join("\n")
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
57
62
|
});
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import colors from '@shopify/cli-kit/node/colors';
|
|
4
|
+
import { outputWarn, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
|
|
5
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
6
|
+
import { writeFile } from '@shopify/cli-kit/node/fs';
|
|
7
|
+
import { ensureIsClean, getLatestGitCommit, GitDirectoryNotCleanError } from '@shopify/cli-kit/node/git';
|
|
8
|
+
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
9
|
+
import { renderFatalError, renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
10
|
+
import { ciPlatform } from '@shopify/cli-kit/node/context/local';
|
|
11
|
+
import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
|
|
12
|
+
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
13
|
+
import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
|
|
14
|
+
import { runBuild } from './build.js';
|
|
6
15
|
|
|
7
16
|
const deploymentLogger = (message, level = "info") => {
|
|
8
17
|
if (level === "error" || level === "warn") {
|
|
@@ -10,12 +19,17 @@ const deploymentLogger = (message, level = "info") => {
|
|
|
10
19
|
}
|
|
11
20
|
};
|
|
12
21
|
class Deploy extends Command {
|
|
22
|
+
static description = "Builds and deploys a Hydrogen storefront to Oxygen.";
|
|
13
23
|
static flags = {
|
|
14
24
|
"env-branch": Flags.string({
|
|
15
|
-
|
|
16
|
-
description: "Environment branch (tag) for environment to deploy to",
|
|
25
|
+
description: "Environment branch (tag) for environment to deploy to.",
|
|
17
26
|
required: false
|
|
18
27
|
}),
|
|
28
|
+
preview: Flags.boolean({
|
|
29
|
+
description: "Deploys to the Preview environment. Overrides --env-branch and Git metadata.",
|
|
30
|
+
required: false,
|
|
31
|
+
default: false
|
|
32
|
+
}),
|
|
19
33
|
force: Flags.boolean({
|
|
20
34
|
char: "f",
|
|
21
35
|
description: "Forces a deployment to proceed if there are uncommited changes in its Git repository.",
|
|
@@ -23,22 +37,21 @@ class Deploy extends Command {
|
|
|
23
37
|
env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
|
|
24
38
|
required: false
|
|
25
39
|
}),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"public-deployment": Flags.boolean({
|
|
29
|
-
env: "SHOPIFY_HYDROGEN_FLAG_PUBLIC_DEPLOYMENT",
|
|
30
|
-
description: "Marks a preview deployment as publicly accessible.",
|
|
40
|
+
"auth-bypass-token": Flags.boolean({
|
|
41
|
+
description: "Generate an authentication bypass token, which can be used to perform end-to-end tests against the deployment.",
|
|
31
42
|
required: false,
|
|
32
43
|
default: false
|
|
33
44
|
}),
|
|
45
|
+
path: commonFlags.path,
|
|
46
|
+
shop: commonFlags.shop,
|
|
34
47
|
"no-json-output": Flags.boolean({
|
|
35
|
-
description: "Prevents the command from creating a JSON file containing the deployment URL
|
|
48
|
+
description: "Prevents the command from creating a JSON file containing the deployment URL in CI environments.",
|
|
36
49
|
required: false,
|
|
37
50
|
default: false
|
|
38
51
|
}),
|
|
39
52
|
token: Flags.string({
|
|
40
53
|
char: "t",
|
|
41
|
-
description: "Oxygen deployment token",
|
|
54
|
+
description: "Oxygen deployment token. Defaults to the linked storefront's token if available.",
|
|
42
55
|
env: "SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN",
|
|
43
56
|
required: false
|
|
44
57
|
}),
|
|
@@ -48,9 +61,9 @@ class Deploy extends Command {
|
|
|
48
61
|
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_DESCRIPTION"
|
|
49
62
|
}),
|
|
50
63
|
"metadata-url": Flags.string({
|
|
51
|
-
description: "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
|
|
52
64
|
required: false,
|
|
53
|
-
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL"
|
|
65
|
+
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL",
|
|
66
|
+
hidden: true
|
|
54
67
|
}),
|
|
55
68
|
"metadata-user": Flags.string({
|
|
56
69
|
description: "User that initiated the deployment. Will be saved and displayed in the Shopify admin",
|
|
@@ -58,20 +71,297 @@ class Deploy extends Command {
|
|
|
58
71
|
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_USER"
|
|
59
72
|
}),
|
|
60
73
|
"metadata-version": Flags.string({
|
|
61
|
-
description: "A version identifier for the deployment. Will be saved and displayed in the Shopify admin",
|
|
62
74
|
required: false,
|
|
63
|
-
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION"
|
|
75
|
+
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION",
|
|
76
|
+
hidden: true
|
|
64
77
|
})
|
|
65
78
|
};
|
|
66
|
-
static hidden = true;
|
|
67
79
|
async run() {
|
|
80
|
+
const { flags } = await this.parse(Deploy);
|
|
81
|
+
const deploymentOptions = this.flagsToOxygenDeploymentOptions(flags);
|
|
82
|
+
await oxygenDeploy(deploymentOptions).catch((error) => {
|
|
83
|
+
renderFatalError(error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}).finally(() => {
|
|
86
|
+
process.exit(0);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
flagsToOxygenDeploymentOptions(flags) {
|
|
90
|
+
const camelFlags = flagsToCamelObject(flags);
|
|
91
|
+
return {
|
|
92
|
+
...camelFlags,
|
|
93
|
+
defaultEnvironment: flags.preview,
|
|
94
|
+
environmentTag: flags["env-branch"],
|
|
95
|
+
path: flags.path ? resolvePath(flags.path) : process.cwd()
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function createUnexpectedAbortError(message) {
|
|
100
|
+
return new AbortError(
|
|
101
|
+
message || "The deployment failed due to an unexpected error.",
|
|
102
|
+
"Retrying the deployement may succeed.",
|
|
103
|
+
[
|
|
104
|
+
[
|
|
105
|
+
"If the issue persits, please check the",
|
|
106
|
+
{
|
|
107
|
+
link: {
|
|
108
|
+
label: "Shopify status page",
|
|
109
|
+
url: "https://status.shopify.com/"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"for any known issues."
|
|
113
|
+
]
|
|
114
|
+
]
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
async function oxygenDeploy(options) {
|
|
118
|
+
const {
|
|
119
|
+
authBypassToken: generateAuthBypassToken,
|
|
120
|
+
defaultEnvironment,
|
|
121
|
+
environmentTag,
|
|
122
|
+
force: forceOnUncommitedChanges,
|
|
123
|
+
noJsonOutput,
|
|
124
|
+
path,
|
|
125
|
+
shop,
|
|
126
|
+
metadataUrl,
|
|
127
|
+
metadataUser,
|
|
128
|
+
metadataVersion
|
|
129
|
+
} = options;
|
|
130
|
+
let { metadataDescription } = options;
|
|
131
|
+
let isCleanGit = true;
|
|
132
|
+
try {
|
|
133
|
+
await ensureIsClean(path);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof GitDirectoryNotCleanError) {
|
|
136
|
+
isCleanGit = false;
|
|
137
|
+
}
|
|
138
|
+
if (!forceOnUncommitedChanges && !isCleanGit) {
|
|
139
|
+
throw new AbortError("Uncommitted changes detected.", null, [
|
|
140
|
+
[
|
|
141
|
+
"Commit your changes before deploying or use the ",
|
|
142
|
+
{ command: "--force" },
|
|
143
|
+
" flag to deploy with uncommitted changes."
|
|
144
|
+
]
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const isCI = ciPlatform().isCI;
|
|
149
|
+
let token = options.token;
|
|
150
|
+
let branch;
|
|
151
|
+
let commitHash;
|
|
152
|
+
let deploymentData;
|
|
153
|
+
let deploymentEnvironmentTag = void 0;
|
|
154
|
+
let gitCommit;
|
|
155
|
+
try {
|
|
156
|
+
gitCommit = await getLatestGitCommit(path);
|
|
157
|
+
branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1];
|
|
158
|
+
commitHash = gitCommit.hash;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
outputWarn("Could not retrieve Git history.");
|
|
161
|
+
branch = void 0;
|
|
162
|
+
}
|
|
163
|
+
if (!metadataDescription && !isCleanGit) {
|
|
68
164
|
renderWarning({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
165
|
+
headline: "No deployment description provided",
|
|
166
|
+
body: [
|
|
167
|
+
"Deploying uncommited changes, but no description has been provided. Use the ",
|
|
168
|
+
{ command: "--metadata-description" },
|
|
169
|
+
"flag to provide a description. If no description is provided, the description defaults to ",
|
|
170
|
+
{ userInput: "<sha> with additional changes" },
|
|
171
|
+
" using the SHA of the last commit."
|
|
72
172
|
]
|
|
73
173
|
});
|
|
174
|
+
metadataDescription = `${commitHash} with additional changes`;
|
|
175
|
+
}
|
|
176
|
+
if (!isCI) {
|
|
177
|
+
deploymentData = await getOxygenDeploymentData({
|
|
178
|
+
root: path,
|
|
179
|
+
flagShop: shop
|
|
180
|
+
});
|
|
181
|
+
if (!deploymentData) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
token = token || deploymentData.oxygenDeploymentToken;
|
|
185
|
+
}
|
|
186
|
+
if (!token) {
|
|
187
|
+
const errMessage = isCI ? [
|
|
188
|
+
"No deployment token provided. Use the ",
|
|
189
|
+
{ command: "--token" },
|
|
190
|
+
" flag to provide a token."
|
|
191
|
+
] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
|
|
192
|
+
throw new AbortError(errMessage);
|
|
193
|
+
}
|
|
194
|
+
if (!isCI && !defaultEnvironment && !environmentTag && deploymentData?.environments) {
|
|
195
|
+
if (deploymentData.environments.length > 1) {
|
|
196
|
+
const choices = [
|
|
197
|
+
...deploymentData.environments.map(({ name, branch: branch2, type }) => ({
|
|
198
|
+
label: name,
|
|
199
|
+
// The preview environment will never have an associated branch so
|
|
200
|
+
// we're using a custom string here to identify it later in our code.
|
|
201
|
+
// Using a period at the end of the value is an invalid branch name
|
|
202
|
+
// in Git so we can be sure that this won't conflict with a merchant's
|
|
203
|
+
// repository.
|
|
204
|
+
value: type === "PREVIEW" ? "shopify-preview-environment." : branch2
|
|
205
|
+
}))
|
|
206
|
+
];
|
|
207
|
+
deploymentEnvironmentTag = await renderSelectPrompt({
|
|
208
|
+
message: "Select an environment to deploy to",
|
|
209
|
+
choices,
|
|
210
|
+
defaultValue: branch
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
outputInfo(
|
|
214
|
+
`Using current checked out branch ${branch} as environment tag`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
let deploymentUrl = "https://oxygen.shopifyapps.com";
|
|
219
|
+
if (process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL) {
|
|
220
|
+
deploymentUrl = process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL;
|
|
221
|
+
outputWarn(
|
|
222
|
+
"Using a custom deployment service. Don't do this in production!"
|
|
223
|
+
);
|
|
74
224
|
}
|
|
225
|
+
let fallbackEnvironmentTag = branch;
|
|
226
|
+
let isPreview = false;
|
|
227
|
+
if (deploymentEnvironmentTag === "shopify-preview-environment.") {
|
|
228
|
+
fallbackEnvironmentTag = void 0;
|
|
229
|
+
deploymentEnvironmentTag = void 0;
|
|
230
|
+
isPreview = true;
|
|
231
|
+
}
|
|
232
|
+
const config = {
|
|
233
|
+
assetsDir: "dist/client",
|
|
234
|
+
bugsnag: true,
|
|
235
|
+
deploymentUrl,
|
|
236
|
+
defaultEnvironment: defaultEnvironment || isPreview,
|
|
237
|
+
deploymentToken: parseToken(token),
|
|
238
|
+
environmentTag: environmentTag || deploymentEnvironmentTag || fallbackEnvironmentTag,
|
|
239
|
+
generateAuthBypassToken,
|
|
240
|
+
verificationMaxDuration: 180,
|
|
241
|
+
metadata: {
|
|
242
|
+
...metadataDescription ? { description: metadataDescription } : {},
|
|
243
|
+
...metadataUrl ? { url: metadataUrl } : {},
|
|
244
|
+
...metadataUser ? { user: metadataUser } : {},
|
|
245
|
+
...metadataVersion ? { version: metadataVersion } : {}
|
|
246
|
+
},
|
|
247
|
+
skipVerification: false,
|
|
248
|
+
rootPath: path,
|
|
249
|
+
skipBuild: false,
|
|
250
|
+
workerOnly: false,
|
|
251
|
+
workerDir: "dist/worker"
|
|
252
|
+
};
|
|
253
|
+
let resolveUpload;
|
|
254
|
+
const uploadPromise = new Promise((resolve) => {
|
|
255
|
+
resolveUpload = resolve;
|
|
256
|
+
});
|
|
257
|
+
let resolveRoutableCheck;
|
|
258
|
+
const routableCheckPromise = new Promise((resolve) => {
|
|
259
|
+
resolveRoutableCheck = resolve;
|
|
260
|
+
});
|
|
261
|
+
let resolveDeploymentCompletedVerification;
|
|
262
|
+
const deploymentCompletedVerificationPromise = new Promise(
|
|
263
|
+
(resolve) => {
|
|
264
|
+
resolveDeploymentCompletedVerification = resolve;
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
let deployError = null;
|
|
268
|
+
let resolveDeploy;
|
|
269
|
+
let rejectDeploy;
|
|
270
|
+
const deployPromise = new Promise((resolve, reject) => {
|
|
271
|
+
resolveDeploy = resolve;
|
|
272
|
+
rejectDeploy = reject;
|
|
273
|
+
});
|
|
274
|
+
const hooks = {
|
|
275
|
+
buildFunction: async (assetPath) => {
|
|
276
|
+
outputInfo(
|
|
277
|
+
outputContent`${colors.whiteBright("Building project...")}`.value
|
|
278
|
+
);
|
|
279
|
+
await runBuild({
|
|
280
|
+
directory: path,
|
|
281
|
+
assetPath,
|
|
282
|
+
sourcemap: true,
|
|
283
|
+
useCodegen: false
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
onDeploymentCompleted: () => resolveDeploymentCompletedVerification(),
|
|
287
|
+
onVerificationComplete: () => resolveRoutableCheck(),
|
|
288
|
+
onDeploymentCompletedVerificationError() {
|
|
289
|
+
deployError = new AbortError(
|
|
290
|
+
"Unable to verify the deployment was completed successfully",
|
|
291
|
+
"Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
onDeploymentFailed: (details) => {
|
|
295
|
+
deployError = createUnexpectedAbortError(details.error || details.status);
|
|
296
|
+
},
|
|
297
|
+
onUploadFilesStart: () => uploadStart(),
|
|
298
|
+
onUploadFilesComplete: () => resolveUpload(),
|
|
299
|
+
onVerificationError: (error) => {
|
|
300
|
+
deployError = new AbortError(
|
|
301
|
+
error.message,
|
|
302
|
+
"Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
|
|
303
|
+
);
|
|
304
|
+
},
|
|
305
|
+
onUploadFilesError: (error) => {
|
|
306
|
+
deployError = new AbortError(
|
|
307
|
+
error.message,
|
|
308
|
+
"Check your connection and try again. If the problem persists, try again later or contact support."
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const uploadStart = async () => {
|
|
313
|
+
outputInfo(
|
|
314
|
+
outputContent`${colors.whiteBright("Deploying to Oxygen..\n")}`.value
|
|
315
|
+
);
|
|
316
|
+
await renderTasks([
|
|
317
|
+
{
|
|
318
|
+
title: "Uploading files",
|
|
319
|
+
task: async () => await uploadPromise
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
title: "Verifying deployment has been completed",
|
|
323
|
+
task: async () => await deploymentCompletedVerificationPromise
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
title: "Verifying deployment is routable",
|
|
327
|
+
task: async () => await routableCheckPromise
|
|
328
|
+
}
|
|
329
|
+
]);
|
|
330
|
+
};
|
|
331
|
+
await createDeploy({ config, hooks, logger: deploymentLogger }).then(async (completedDeployment) => {
|
|
332
|
+
if (!completedDeployment) {
|
|
333
|
+
rejectDeploy(createUnexpectedAbortError());
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const nextSteps = [
|
|
337
|
+
[
|
|
338
|
+
"Open",
|
|
339
|
+
{ link: { url: completedDeployment.url } },
|
|
340
|
+
`in your browser to view your deployment.`
|
|
341
|
+
]
|
|
342
|
+
];
|
|
343
|
+
if (completedDeployment?.authBypassToken) {
|
|
344
|
+
nextSteps.push([
|
|
345
|
+
"Use the",
|
|
346
|
+
{ subdued: completedDeployment.authBypassToken },
|
|
347
|
+
"token to perform end-to-end tests against the deployment."
|
|
348
|
+
]);
|
|
349
|
+
}
|
|
350
|
+
renderSuccess({
|
|
351
|
+
body: ["Successfully deployed to Oxygen"],
|
|
352
|
+
nextSteps
|
|
353
|
+
});
|
|
354
|
+
if (isCI && !noJsonOutput) {
|
|
355
|
+
await writeFile(
|
|
356
|
+
"h2_deploy_log.json",
|
|
357
|
+
JSON.stringify(completedDeployment)
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
resolveDeploy();
|
|
361
|
+
}).catch((error) => {
|
|
362
|
+
rejectDeploy(deployError || error);
|
|
363
|
+
});
|
|
364
|
+
return deployPromise;
|
|
75
365
|
}
|
|
76
366
|
|
|
77
|
-
export { Deploy as default, deploymentLogger };
|
|
367
|
+
export { Deploy as default, deploymentLogger, oxygenDeploy };
|