@shopify/cli-hydrogen 6.0.2 → 7.0.0
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 +173 -37
- package/dist/commands/hydrogen/deploy.test.js +192 -20
- package/dist/commands/hydrogen/dev.js +56 -31
- 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 +22 -11
- package/dist/commands/hydrogen/setup.js +0 -4
- package/dist/commands/hydrogen/setup.test.js +0 -1
- package/dist/commands/hydrogen/shortcut.js +1 -0
- package/dist/commands/hydrogen/upgrade.js +720 -0
- package/dist/commands/hydrogen/upgrade.test.js +786 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
- package/dist/generator-templates/starter/CHANGELOG.md +126 -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/Footer.tsx +3 -1
- package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
- package/dist/generator-templates/starter/app/components/Layout.tsx +14 -11
- 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/[robots.txt].tsx +0 -27
- 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 -10
- package/dist/generator-templates/starter/remix.config.js +4 -0
- package/dist/generator-templates/starter/remix.env.d.ts +6 -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/check-lockfile.js +1 -0
- package/dist/lib/codegen.js +59 -18
- package/dist/lib/defer.js +12 -0
- package/dist/lib/file.js +52 -3
- package/dist/lib/flags.js +27 -9
- package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
- 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 +32 -14
- package/dist/lib/mini-oxygen/assets.js +118 -0
- package/dist/lib/mini-oxygen/common.js +2 -1
- package/dist/lib/mini-oxygen/index.js +7 -5
- package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
- package/dist/lib/mini-oxygen/node.js +19 -5
- package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
- package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
- package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
- package/dist/lib/mini-oxygen/workerd.js +74 -50
- package/dist/lib/missing-routes.js +6 -3
- package/dist/lib/onboarding/common.js +40 -9
- package/dist/lib/onboarding/local.js +19 -11
- package/dist/lib/onboarding/remote.js +48 -28
- package/dist/lib/render-errors.js +2 -0
- 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 +56 -38
- package/dist/lib/shell.js +1 -1
- package/dist/lib/template-diff.js +89 -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 +134 -59
- package/package.json +18 -26
- 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
|
}
|
|
@@ -3,9 +3,10 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
3
3
|
import colors from '@shopify/cli-kit/node/colors';
|
|
4
4
|
import { outputWarn, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
|
|
5
5
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
6
|
-
import {
|
|
6
|
+
import { writeFile } from '@shopify/cli-kit/node/fs';
|
|
7
|
+
import { ensureIsClean, getLatestGitCommit, GitDirectoryNotCleanError } from '@shopify/cli-kit/node/git';
|
|
7
8
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
8
|
-
import { renderFatalError, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
9
|
+
import { renderFatalError, renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
9
10
|
import { ciPlatform } from '@shopify/cli-kit/node/context/local';
|
|
10
11
|
import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
|
|
11
12
|
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
@@ -18,30 +19,51 @@ const deploymentLogger = (message, level = "info") => {
|
|
|
18
19
|
}
|
|
19
20
|
};
|
|
20
21
|
class Deploy extends Command {
|
|
22
|
+
static description = "Builds and deploys a Hydrogen storefront to Oxygen.";
|
|
21
23
|
static flags = {
|
|
22
24
|
"env-branch": Flags.string({
|
|
23
|
-
|
|
24
|
-
description: "Environment branch (tag) for environment to deploy to",
|
|
25
|
+
description: "Environment branch (tag) for environment to deploy to.",
|
|
25
26
|
required: false
|
|
26
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
|
+
}),
|
|
33
|
+
force: Flags.boolean({
|
|
34
|
+
char: "f",
|
|
35
|
+
description: "Forces a deployment to proceed if there are uncommited changes in its Git repository.",
|
|
36
|
+
default: false,
|
|
37
|
+
env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
|
|
38
|
+
required: false
|
|
39
|
+
}),
|
|
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.",
|
|
42
|
+
required: false,
|
|
43
|
+
default: false
|
|
44
|
+
}),
|
|
27
45
|
path: commonFlags.path,
|
|
28
46
|
shop: commonFlags.shop,
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
description: "Marks a preview deployment as publicly accessible.",
|
|
47
|
+
"no-json-output": Flags.boolean({
|
|
48
|
+
description: "Prevents the command from creating a JSON file containing the deployment URL in CI environments.",
|
|
32
49
|
required: false,
|
|
33
50
|
default: false
|
|
34
51
|
}),
|
|
35
52
|
token: Flags.string({
|
|
36
53
|
char: "t",
|
|
37
|
-
description: "Oxygen deployment token",
|
|
54
|
+
description: "Oxygen deployment token. Defaults to the linked storefront's token if available.",
|
|
38
55
|
env: "SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN",
|
|
39
56
|
required: false
|
|
40
57
|
}),
|
|
58
|
+
"metadata-description": Flags.string({
|
|
59
|
+
description: "Description of the changes in the deployment. Defaults to the commit message of the latest commit if there are no uncommited changes.",
|
|
60
|
+
required: false,
|
|
61
|
+
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_DESCRIPTION"
|
|
62
|
+
}),
|
|
41
63
|
"metadata-url": Flags.string({
|
|
42
|
-
description: "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
|
|
43
64
|
required: false,
|
|
44
|
-
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL"
|
|
65
|
+
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL",
|
|
66
|
+
hidden: true
|
|
45
67
|
}),
|
|
46
68
|
"metadata-user": Flags.string({
|
|
47
69
|
description: "User that initiated the deployment. Will be saved and displayed in the Shopify admin",
|
|
@@ -49,12 +71,11 @@ class Deploy extends Command {
|
|
|
49
71
|
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_USER"
|
|
50
72
|
}),
|
|
51
73
|
"metadata-version": Flags.string({
|
|
52
|
-
description: "A version identifier for the deployment. Will be saved and displayed in the Shopify admin",
|
|
53
74
|
required: false,
|
|
54
|
-
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION"
|
|
75
|
+
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION",
|
|
76
|
+
hidden: true
|
|
55
77
|
})
|
|
56
78
|
};
|
|
57
|
-
static hidden = true;
|
|
58
79
|
async run() {
|
|
59
80
|
const { flags } = await this.parse(Deploy);
|
|
60
81
|
const deploymentOptions = this.flagsToOxygenDeploymentOptions(flags);
|
|
@@ -69,35 +90,90 @@ class Deploy extends Command {
|
|
|
69
90
|
const camelFlags = flagsToCamelObject(flags);
|
|
70
91
|
return {
|
|
71
92
|
...camelFlags,
|
|
93
|
+
defaultEnvironment: flags.preview,
|
|
72
94
|
environmentTag: flags["env-branch"],
|
|
73
95
|
path: flags.path ? resolvePath(flags.path) : process.cwd()
|
|
74
96
|
};
|
|
75
97
|
}
|
|
76
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
|
+
}
|
|
77
117
|
async function oxygenDeploy(options) {
|
|
78
118
|
const {
|
|
119
|
+
authBypassToken: generateAuthBypassToken,
|
|
120
|
+
defaultEnvironment,
|
|
79
121
|
environmentTag,
|
|
122
|
+
force: forceOnUncommitedChanges,
|
|
123
|
+
noJsonOutput,
|
|
80
124
|
path,
|
|
81
125
|
shop,
|
|
82
|
-
publicDeployment,
|
|
83
126
|
metadataUrl,
|
|
84
127
|
metadataUser,
|
|
85
128
|
metadataVersion
|
|
86
129
|
} = options;
|
|
87
|
-
|
|
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;
|
|
88
149
|
let token = options.token;
|
|
89
150
|
let branch;
|
|
151
|
+
let commitHash;
|
|
90
152
|
let deploymentData;
|
|
91
153
|
let deploymentEnvironmentTag = void 0;
|
|
92
154
|
let gitCommit;
|
|
93
155
|
try {
|
|
94
156
|
gitCommit = await getLatestGitCommit(path);
|
|
95
157
|
branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1];
|
|
158
|
+
commitHash = gitCommit.hash;
|
|
96
159
|
} catch (error) {
|
|
97
160
|
outputWarn("Could not retrieve Git history.");
|
|
98
161
|
branch = void 0;
|
|
99
162
|
}
|
|
100
|
-
if (!
|
|
163
|
+
if (!metadataDescription && !isCleanGit) {
|
|
164
|
+
renderWarning({
|
|
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."
|
|
172
|
+
]
|
|
173
|
+
});
|
|
174
|
+
metadataDescription = `${commitHash} with additional changes`;
|
|
175
|
+
}
|
|
176
|
+
if (!isCI) {
|
|
101
177
|
deploymentData = await getOxygenDeploymentData({
|
|
102
178
|
root: path,
|
|
103
179
|
flagShop: shop
|
|
@@ -108,19 +184,24 @@ async function oxygenDeploy(options) {
|
|
|
108
184
|
token = token || deploymentData.oxygenDeploymentToken;
|
|
109
185
|
}
|
|
110
186
|
if (!token) {
|
|
111
|
-
const errMessage =
|
|
187
|
+
const errMessage = isCI ? [
|
|
112
188
|
"No deployment token provided. Use the ",
|
|
113
189
|
{ command: "--token" },
|
|
114
190
|
" flag to provide a token."
|
|
115
191
|
] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
|
|
116
192
|
throw new AbortError(errMessage);
|
|
117
193
|
}
|
|
118
|
-
if (!
|
|
194
|
+
if (!isCI && !defaultEnvironment && !environmentTag && deploymentData?.environments) {
|
|
119
195
|
if (deploymentData.environments.length > 1) {
|
|
120
196
|
const choices = [
|
|
121
|
-
...deploymentData.environments.map(({ name, branch: branch2 }) => ({
|
|
197
|
+
...deploymentData.environments.map(({ name, branch: branch2, type }) => ({
|
|
122
198
|
label: name,
|
|
123
|
-
|
|
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
|
|
124
205
|
}))
|
|
125
206
|
];
|
|
126
207
|
deploymentEnvironmentTag = await renderSelectPrompt({
|
|
@@ -134,19 +215,35 @@ async function oxygenDeploy(options) {
|
|
|
134
215
|
);
|
|
135
216
|
}
|
|
136
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
|
+
);
|
|
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
|
+
}
|
|
137
232
|
const config = {
|
|
138
233
|
assetsDir: "dist/client",
|
|
139
234
|
bugsnag: true,
|
|
140
|
-
deploymentUrl
|
|
235
|
+
deploymentUrl,
|
|
236
|
+
defaultEnvironment: defaultEnvironment || isPreview,
|
|
141
237
|
deploymentToken: parseToken(token),
|
|
142
|
-
environmentTag: environmentTag || deploymentEnvironmentTag ||
|
|
238
|
+
environmentTag: environmentTag || deploymentEnvironmentTag || fallbackEnvironmentTag,
|
|
239
|
+
generateAuthBypassToken,
|
|
143
240
|
verificationMaxDuration: 180,
|
|
144
241
|
metadata: {
|
|
242
|
+
...metadataDescription ? { description: metadataDescription } : {},
|
|
145
243
|
...metadataUrl ? { url: metadataUrl } : {},
|
|
146
244
|
...metadataUser ? { user: metadataUser } : {},
|
|
147
245
|
...metadataVersion ? { version: metadataVersion } : {}
|
|
148
246
|
},
|
|
149
|
-
publicDeployment,
|
|
150
247
|
skipVerification: false,
|
|
151
248
|
rootPath: path,
|
|
152
249
|
skipBuild: false,
|
|
@@ -157,10 +254,16 @@ async function oxygenDeploy(options) {
|
|
|
157
254
|
const uploadPromise = new Promise((resolve) => {
|
|
158
255
|
resolveUpload = resolve;
|
|
159
256
|
});
|
|
160
|
-
let
|
|
161
|
-
const
|
|
162
|
-
|
|
257
|
+
let resolveRoutableCheck;
|
|
258
|
+
const routableCheckPromise = new Promise((resolve) => {
|
|
259
|
+
resolveRoutableCheck = resolve;
|
|
163
260
|
});
|
|
261
|
+
let resolveDeploymentCompletedVerification;
|
|
262
|
+
const deploymentCompletedVerificationPromise = new Promise(
|
|
263
|
+
(resolve) => {
|
|
264
|
+
resolveDeploymentCompletedVerification = resolve;
|
|
265
|
+
}
|
|
266
|
+
);
|
|
164
267
|
let deployError = null;
|
|
165
268
|
let resolveDeploy;
|
|
166
269
|
let rejectDeploy;
|
|
@@ -176,11 +279,21 @@ async function oxygenDeploy(options) {
|
|
|
176
279
|
await runBuild({
|
|
177
280
|
directory: path,
|
|
178
281
|
assetPath,
|
|
179
|
-
sourcemap:
|
|
282
|
+
sourcemap: true,
|
|
180
283
|
useCodegen: false
|
|
181
284
|
});
|
|
182
285
|
},
|
|
183
|
-
|
|
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
|
+
},
|
|
184
297
|
onUploadFilesStart: () => uploadStart(),
|
|
185
298
|
onUploadFilesComplete: () => resolveUpload(),
|
|
186
299
|
onVerificationError: (error) => {
|
|
@@ -206,21 +319,44 @@ async function oxygenDeploy(options) {
|
|
|
206
319
|
task: async () => await uploadPromise
|
|
207
320
|
},
|
|
208
321
|
{
|
|
209
|
-
title: "Verifying deployment",
|
|
210
|
-
task: async () => await
|
|
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
|
|
211
328
|
}
|
|
212
329
|
]);
|
|
213
330
|
};
|
|
214
|
-
await createDeploy({ config, hooks, logger: deploymentLogger }).then((
|
|
215
|
-
|
|
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
|
+
}
|
|
216
350
|
renderSuccess({
|
|
217
351
|
body: ["Successfully deployed to Oxygen"],
|
|
218
|
-
nextSteps
|
|
219
|
-
[
|
|
220
|
-
`Open ${url} in your browser to view your ${deploymentType} deployment`
|
|
221
|
-
]
|
|
222
|
-
]
|
|
352
|
+
nextSteps
|
|
223
353
|
});
|
|
354
|
+
if (isCI && !noJsonOutput) {
|
|
355
|
+
await writeFile(
|
|
356
|
+
"h2_deploy_log.json",
|
|
357
|
+
JSON.stringify(completedDeployment)
|
|
358
|
+
);
|
|
359
|
+
}
|
|
224
360
|
resolveDeploy();
|
|
225
361
|
}).catch((error) => {
|
|
226
362
|
rejectDeploy(deployError || error);
|