@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
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
import cliTruncate from 'cli-truncate';
|
|
6
|
+
import { Flags } from '@oclif/core';
|
|
7
|
+
import { ensureInsideGitDirectory, isClean } from '@shopify/cli-kit/node/git';
|
|
8
|
+
import Command from '@shopify/cli-kit/node/base-command';
|
|
9
|
+
import { renderSuccess, renderInfo, renderConfirmationPrompt, renderTasks, renderSelectPrompt } from '@shopify/cli-kit/node/ui';
|
|
10
|
+
import { readFile, isDirectory, mkdir, fileExists, touchFile, removeFile, writeFile } from '@shopify/cli-kit/node/fs';
|
|
11
|
+
import { installNodeModules, getPackageManager, getDependencies } from '@shopify/cli-kit/node/node-package-manager';
|
|
12
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
13
|
+
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
14
|
+
import { getProjectPaths } from '../../lib/remix-config.js';
|
|
15
|
+
|
|
16
|
+
const INSTRUCTIONS_FOLDER = ".hydrogen";
|
|
17
|
+
class Upgrade extends Command {
|
|
18
|
+
static description = "Upgrade Remix and Hydrogen npm dependencies.";
|
|
19
|
+
static flags = {
|
|
20
|
+
path: commonFlags.path,
|
|
21
|
+
version: Flags.string({
|
|
22
|
+
description: "A target hydrogen version to update to",
|
|
23
|
+
required: false,
|
|
24
|
+
char: "v"
|
|
25
|
+
}),
|
|
26
|
+
force: Flags.boolean({
|
|
27
|
+
description: "Ignore warnings and force the upgrade to the target version",
|
|
28
|
+
env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
|
|
29
|
+
char: "f"
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { flags } = await this.parse(Upgrade);
|
|
34
|
+
await runUpgrade({
|
|
35
|
+
...flagsToCamelObject(flags),
|
|
36
|
+
appPath: flags.path ? path.resolve(flags.path) : process.cwd()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let CACHED_CHANGELOG = null;
|
|
41
|
+
async function runUpgrade({
|
|
42
|
+
appPath,
|
|
43
|
+
version: targetVersion,
|
|
44
|
+
force
|
|
45
|
+
}) {
|
|
46
|
+
if (!force) {
|
|
47
|
+
await checkIsGitRepo(appPath);
|
|
48
|
+
await checkDirtyGitBranch(appPath);
|
|
49
|
+
}
|
|
50
|
+
const { currentVersion, currentDependencies } = await getHydrogenVersion({
|
|
51
|
+
appPath
|
|
52
|
+
});
|
|
53
|
+
const isPrerelease = semver.prerelease(currentVersion);
|
|
54
|
+
if (isPrerelease) {
|
|
55
|
+
throw new AbortError(
|
|
56
|
+
"The upgrade command cannot be run over a prerelease Hydrogen version"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const changelog = await getChangelog();
|
|
60
|
+
const { availableUpgrades } = getAvailableUpgrades({
|
|
61
|
+
releases: changelog.releases,
|
|
62
|
+
currentVersion,
|
|
63
|
+
currentDependencies
|
|
64
|
+
});
|
|
65
|
+
if (!availableUpgrades?.length) {
|
|
66
|
+
renderSuccess({
|
|
67
|
+
headline: `You are on the latest Hydrogen version: ${getAbsoluteVersion(
|
|
68
|
+
currentVersion
|
|
69
|
+
)}`
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let confirmed = false;
|
|
74
|
+
let selectedRelease = void 0;
|
|
75
|
+
let cumulativeRelease = void 0;
|
|
76
|
+
do {
|
|
77
|
+
selectedRelease = await getSelectedRelease({
|
|
78
|
+
currentVersion,
|
|
79
|
+
targetVersion,
|
|
80
|
+
availableUpgrades
|
|
81
|
+
});
|
|
82
|
+
cumulativeRelease = getCummulativeRelease({
|
|
83
|
+
availableUpgrades,
|
|
84
|
+
currentVersion,
|
|
85
|
+
currentDependencies,
|
|
86
|
+
selectedRelease
|
|
87
|
+
});
|
|
88
|
+
confirmed = await displayConfirmation({
|
|
89
|
+
cumulativeRelease,
|
|
90
|
+
selectedRelease
|
|
91
|
+
});
|
|
92
|
+
} while (!confirmed);
|
|
93
|
+
const instrunctionsFilePathPromise = generateUpgradeInstructionsFile({
|
|
94
|
+
appPath,
|
|
95
|
+
cumulativeRelease,
|
|
96
|
+
currentVersion,
|
|
97
|
+
selectedRelease
|
|
98
|
+
});
|
|
99
|
+
await upgradeNodeModules({ appPath, selectedRelease, currentDependencies });
|
|
100
|
+
await validateUpgrade({
|
|
101
|
+
appPath,
|
|
102
|
+
selectedRelease
|
|
103
|
+
});
|
|
104
|
+
const instrunctionsFilePath = await instrunctionsFilePathPromise;
|
|
105
|
+
await displayUpgradeSummary({
|
|
106
|
+
appPath,
|
|
107
|
+
currentVersion,
|
|
108
|
+
instrunctionsFilePath,
|
|
109
|
+
selectedRelease
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function checkIsGitRepo(appPath) {
|
|
113
|
+
return ensureInsideGitDirectory(appPath).catch(() => {
|
|
114
|
+
throw new AbortError(
|
|
115
|
+
"The upgrade command can only be run on a git repository",
|
|
116
|
+
`Please run the command inside a git repository or run 'git init' to create one`
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function checkDirtyGitBranch(appPath) {
|
|
121
|
+
if (!await isClean(appPath)) {
|
|
122
|
+
throw new AbortError(
|
|
123
|
+
"The upgrade command can only be run on a clean git branch",
|
|
124
|
+
"Please commit your changes or re-run the command on a clean branch"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function getHydrogenVersion({ appPath }) {
|
|
129
|
+
const { root } = getProjectPaths(appPath);
|
|
130
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
131
|
+
let packageJson;
|
|
132
|
+
try {
|
|
133
|
+
packageJson = JSON.parse(await readFile(packageJsonPath));
|
|
134
|
+
} catch {
|
|
135
|
+
throw new AbortError(
|
|
136
|
+
"Could not find a valid package.json",
|
|
137
|
+
"Please make sure you are running the command in a npm project"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const currentDependencies = {
|
|
141
|
+
...packageJson.dependencies ?? {},
|
|
142
|
+
...packageJson.devDependencies ?? {}
|
|
143
|
+
};
|
|
144
|
+
const currentVersion = currentDependencies["@shopify/hydrogen"];
|
|
145
|
+
if (!currentVersion) {
|
|
146
|
+
throw new AbortError(
|
|
147
|
+
"Could not find a valid Hydrogen version in package.json",
|
|
148
|
+
"Please make sure you are running the command in a Hydrogen project"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return { currentVersion, currentDependencies };
|
|
152
|
+
}
|
|
153
|
+
async function getChangelog() {
|
|
154
|
+
if (CACHED_CHANGELOG)
|
|
155
|
+
return CACHED_CHANGELOG;
|
|
156
|
+
if (process.env.FORCE_CHANGELOG_SOURCE === "local" || process.env.FORCE_CHANGELOG_SOURCE !== "remote" && !!process.env.LOCAL_ENV) {
|
|
157
|
+
const require2 = createRequire(import.meta.url);
|
|
158
|
+
return require2(fileURLToPath(
|
|
159
|
+
new URL("../../../../../docs/changelog.json", import.meta.url)
|
|
160
|
+
));
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetch("https://hydrogen.shopify.dev/changelog.json");
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new Error("Failed to fetch changelog.json");
|
|
166
|
+
}
|
|
167
|
+
const json = await response.json();
|
|
168
|
+
if ("releases" in json && "url" in json) {
|
|
169
|
+
CACHED_CHANGELOG = json;
|
|
170
|
+
return CACHED_CHANGELOG;
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
174
|
+
throw new AbortError(
|
|
175
|
+
"Failed to fetch changelog",
|
|
176
|
+
"Ensure you have internet connection and try again"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
function hasOutdatedDependencies({
|
|
180
|
+
release,
|
|
181
|
+
currentDependencies
|
|
182
|
+
}) {
|
|
183
|
+
return Object.entries(release.dependencies).some(([name, version]) => {
|
|
184
|
+
const currentDependencyVersion = currentDependencies?.[name];
|
|
185
|
+
if (!currentDependencyVersion)
|
|
186
|
+
return false;
|
|
187
|
+
const isDependencyOutdated = semver.gt(
|
|
188
|
+
getAbsoluteVersion(version),
|
|
189
|
+
getAbsoluteVersion(currentDependencyVersion)
|
|
190
|
+
);
|
|
191
|
+
return isDependencyOutdated;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function isUpgradeableRelease({
|
|
195
|
+
currentDependencies,
|
|
196
|
+
currentPinnedVersion,
|
|
197
|
+
release
|
|
198
|
+
}) {
|
|
199
|
+
if (!currentDependencies)
|
|
200
|
+
return false;
|
|
201
|
+
const isHydrogenOutdated = semver.gt(release.version, currentPinnedVersion);
|
|
202
|
+
if (isHydrogenOutdated)
|
|
203
|
+
return true;
|
|
204
|
+
const isCurrentHydrogen = getAbsoluteVersion(release.version) === currentPinnedVersion;
|
|
205
|
+
if (!isCurrentHydrogen)
|
|
206
|
+
return false;
|
|
207
|
+
return hasOutdatedDependencies({ release, currentDependencies });
|
|
208
|
+
}
|
|
209
|
+
function getAvailableUpgrades({
|
|
210
|
+
releases,
|
|
211
|
+
currentVersion,
|
|
212
|
+
currentDependencies
|
|
213
|
+
}) {
|
|
214
|
+
const currentPinnedVersion = getAbsoluteVersion(currentVersion);
|
|
215
|
+
let currentMajorVersion = "";
|
|
216
|
+
const availableUpgrades = releases.filter((release) => {
|
|
217
|
+
const isUpgradeable = isUpgradeableRelease({
|
|
218
|
+
release,
|
|
219
|
+
currentPinnedVersion,
|
|
220
|
+
currentDependencies
|
|
221
|
+
});
|
|
222
|
+
if (!isUpgradeable)
|
|
223
|
+
return false;
|
|
224
|
+
if (currentMajorVersion !== release.version) {
|
|
225
|
+
currentMajorVersion = release.version;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
});
|
|
230
|
+
const uniqueAvailableUpgrades = availableUpgrades.reduce((acc, release) => {
|
|
231
|
+
if (acc[release.version])
|
|
232
|
+
return acc;
|
|
233
|
+
acc[release.version] = release;
|
|
234
|
+
return acc;
|
|
235
|
+
}, {});
|
|
236
|
+
return { availableUpgrades, uniqueAvailableUpgrades };
|
|
237
|
+
}
|
|
238
|
+
async function getSelectedRelease({
|
|
239
|
+
targetVersion,
|
|
240
|
+
availableUpgrades,
|
|
241
|
+
currentVersion
|
|
242
|
+
}) {
|
|
243
|
+
const targetRelease = targetVersion ? availableUpgrades.find(
|
|
244
|
+
(release) => getAbsoluteVersion(release.version) === getAbsoluteVersion(targetVersion)
|
|
245
|
+
) : void 0;
|
|
246
|
+
return targetRelease ?? promptUpgradeOptions(currentVersion, availableUpgrades);
|
|
247
|
+
}
|
|
248
|
+
function getCummulativeRelease({
|
|
249
|
+
availableUpgrades,
|
|
250
|
+
selectedRelease,
|
|
251
|
+
currentVersion,
|
|
252
|
+
currentDependencies
|
|
253
|
+
}) {
|
|
254
|
+
const currentPinnedVersion = getAbsoluteVersion(currentVersion);
|
|
255
|
+
if (!availableUpgrades?.length) {
|
|
256
|
+
return { features: [], fixes: [] };
|
|
257
|
+
}
|
|
258
|
+
const upgradingReleases = availableUpgrades.filter((release) => {
|
|
259
|
+
const isHydrogenUpgrade = semver.gt(release.version, currentPinnedVersion) && semver.lte(release.version, selectedRelease.version);
|
|
260
|
+
if (isHydrogenUpgrade)
|
|
261
|
+
return true;
|
|
262
|
+
const isSameHydrogenVersion = getAbsoluteVersion(release.version) === currentPinnedVersion;
|
|
263
|
+
if (!isSameHydrogenVersion || !currentDependencies)
|
|
264
|
+
return false;
|
|
265
|
+
return hasOutdatedDependencies({ release, currentDependencies });
|
|
266
|
+
});
|
|
267
|
+
return upgradingReleases.reduce(
|
|
268
|
+
(acc, release) => {
|
|
269
|
+
acc.features = [...acc.features, ...release.features];
|
|
270
|
+
acc.fixes = [...acc.fixes, ...release.fixes];
|
|
271
|
+
return acc;
|
|
272
|
+
},
|
|
273
|
+
{ features: [], fixes: [] }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
function displayConfirmation({
|
|
277
|
+
cumulativeRelease,
|
|
278
|
+
selectedRelease
|
|
279
|
+
}) {
|
|
280
|
+
const { features, fixes } = cumulativeRelease;
|
|
281
|
+
if (features.length || fixes.length) {
|
|
282
|
+
renderInfo({
|
|
283
|
+
headline: `Included in this upgrade:`,
|
|
284
|
+
//@ts-ignore we know that filter(Boolean) will always return an array
|
|
285
|
+
customSections: [
|
|
286
|
+
features.length && {
|
|
287
|
+
title: "Features",
|
|
288
|
+
body: [
|
|
289
|
+
{
|
|
290
|
+
list: {
|
|
291
|
+
items: features.map((item) => item.title)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
},
|
|
296
|
+
fixes.length && {
|
|
297
|
+
title: "Fixes",
|
|
298
|
+
body: [
|
|
299
|
+
{
|
|
300
|
+
list: {
|
|
301
|
+
items: fixes.map((item) => item.title)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
].filter(Boolean)
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return renderConfirmationPrompt({
|
|
310
|
+
message: `Are you sure you want to upgrade to ${selectedRelease.version}?`,
|
|
311
|
+
cancellationMessage: `No, choose another version`,
|
|
312
|
+
defaultValue: true
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function isRemixDependency([name]) {
|
|
316
|
+
if (name.includes("@remix-run")) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
function maybeIncludeDependency({
|
|
322
|
+
currentDependencies,
|
|
323
|
+
dependency: [name, version],
|
|
324
|
+
selectedRelease
|
|
325
|
+
}) {
|
|
326
|
+
const existingDependencyVersion = currentDependencies[name];
|
|
327
|
+
const isRemixPackage = isRemixDependency([name, version]);
|
|
328
|
+
if (isRemixPackage)
|
|
329
|
+
return false;
|
|
330
|
+
const isNextVersion = existingDependencyVersion === "next";
|
|
331
|
+
if (isNextVersion)
|
|
332
|
+
return false;
|
|
333
|
+
const depMeta = selectedRelease.dependenciesMeta?.[name];
|
|
334
|
+
if (!depMeta)
|
|
335
|
+
return true;
|
|
336
|
+
const isRequired = Boolean(
|
|
337
|
+
selectedRelease.dependenciesMeta?.[name]?.required
|
|
338
|
+
);
|
|
339
|
+
if (!isRequired)
|
|
340
|
+
return false;
|
|
341
|
+
if (!existingDependencyVersion)
|
|
342
|
+
return true;
|
|
343
|
+
const isOlderVersion = semver.lt(
|
|
344
|
+
getAbsoluteVersion(existingDependencyVersion),
|
|
345
|
+
getAbsoluteVersion(version)
|
|
346
|
+
);
|
|
347
|
+
if (isOlderVersion)
|
|
348
|
+
return true;
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
function buildUpgradeCommandArgs({
|
|
352
|
+
selectedRelease,
|
|
353
|
+
currentDependencies
|
|
354
|
+
}) {
|
|
355
|
+
const args = [];
|
|
356
|
+
for (const dependency of Object.entries(selectedRelease.dependencies)) {
|
|
357
|
+
const shouldUpgradeDep = maybeIncludeDependency({
|
|
358
|
+
currentDependencies,
|
|
359
|
+
dependency,
|
|
360
|
+
selectedRelease
|
|
361
|
+
});
|
|
362
|
+
if (!shouldUpgradeDep)
|
|
363
|
+
continue;
|
|
364
|
+
args.push(`${dependency[0]}@${getAbsoluteVersion(dependency[1])}`);
|
|
365
|
+
}
|
|
366
|
+
for (const dependency of Object.entries(selectedRelease.devDependencies)) {
|
|
367
|
+
const shouldUpgradeDep = maybeIncludeDependency({
|
|
368
|
+
currentDependencies,
|
|
369
|
+
dependency,
|
|
370
|
+
selectedRelease
|
|
371
|
+
});
|
|
372
|
+
if (!shouldUpgradeDep)
|
|
373
|
+
continue;
|
|
374
|
+
args.push(`${dependency[0]}@${getAbsoluteVersion(dependency[1])}`);
|
|
375
|
+
}
|
|
376
|
+
const currentRemix = Object.entries(currentDependencies).find(isRemixDependency);
|
|
377
|
+
const selectedRemix = Object.entries(selectedRelease.dependencies).find(
|
|
378
|
+
isRemixDependency
|
|
379
|
+
);
|
|
380
|
+
if (currentRemix && selectedRemix) {
|
|
381
|
+
const shouldUpgradeRemix = semver.lt(
|
|
382
|
+
getAbsoluteVersion(currentRemix[1]),
|
|
383
|
+
getAbsoluteVersion(selectedRemix[1])
|
|
384
|
+
);
|
|
385
|
+
if (shouldUpgradeRemix) {
|
|
386
|
+
args.push(
|
|
387
|
+
...appendRemixDependencies({ currentDependencies, selectedRemix })
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return args;
|
|
392
|
+
}
|
|
393
|
+
async function upgradeNodeModules({
|
|
394
|
+
appPath,
|
|
395
|
+
selectedRelease,
|
|
396
|
+
currentDependencies
|
|
397
|
+
}) {
|
|
398
|
+
await renderTasks(
|
|
399
|
+
[
|
|
400
|
+
{
|
|
401
|
+
title: `Upgrading dependencies`,
|
|
402
|
+
task: async () => {
|
|
403
|
+
await installNodeModules({
|
|
404
|
+
directory: appPath,
|
|
405
|
+
packageManager: await getPackageManager(appPath),
|
|
406
|
+
args: buildUpgradeCommandArgs({
|
|
407
|
+
selectedRelease,
|
|
408
|
+
currentDependencies
|
|
409
|
+
})
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
],
|
|
414
|
+
{}
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
function appendRemixDependencies({
|
|
418
|
+
currentDependencies,
|
|
419
|
+
selectedRemix
|
|
420
|
+
}) {
|
|
421
|
+
const command = [];
|
|
422
|
+
for (const [name, version] of Object.entries(currentDependencies)) {
|
|
423
|
+
const isRemixPackage = isRemixDependency([name, version]);
|
|
424
|
+
if (!isRemixPackage) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
command.push(`${name}@${getAbsoluteVersion(selectedRemix[1])}`);
|
|
428
|
+
}
|
|
429
|
+
return command;
|
|
430
|
+
}
|
|
431
|
+
function getAbsoluteVersion(version) {
|
|
432
|
+
const result = semver.minVersion(version);
|
|
433
|
+
if (!result) {
|
|
434
|
+
throw new AbortError(`Invalid version: ${version}`);
|
|
435
|
+
}
|
|
436
|
+
return result.version;
|
|
437
|
+
}
|
|
438
|
+
async function promptUpgradeOptions(currentVersion, availableUpgrades) {
|
|
439
|
+
if (!availableUpgrades?.length) {
|
|
440
|
+
throw new AbortError("No upgrade options available");
|
|
441
|
+
}
|
|
442
|
+
const choices = availableUpgrades.map((release, index) => {
|
|
443
|
+
const { version, title } = release;
|
|
444
|
+
const tag = index === 0 ? "(latest)" : semver.patch(version) === 0 ? "(major)" : getAbsoluteVersion(currentVersion) === getAbsoluteVersion(version) ? "(outdated)" : "";
|
|
445
|
+
`${semver.major(version)}.${semver.minor(version)}`;
|
|
446
|
+
return {
|
|
447
|
+
// group: majorVersion,
|
|
448
|
+
label: `${version} ${tag} - ${cliTruncate(title, 54)}`,
|
|
449
|
+
value: release
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
return renderSelectPrompt({
|
|
453
|
+
message: `Available Hydrogen versions (current: ${currentVersion})`,
|
|
454
|
+
choices,
|
|
455
|
+
defaultValue: choices[0]?.value
|
|
456
|
+
// Latest version
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
async function displayUpgradeSummary({
|
|
460
|
+
appPath,
|
|
461
|
+
currentVersion,
|
|
462
|
+
selectedRelease,
|
|
463
|
+
instrunctionsFilePath
|
|
464
|
+
}) {
|
|
465
|
+
const updatedDependenciesList = [
|
|
466
|
+
...Object.entries(selectedRelease.dependencies || {}).map(
|
|
467
|
+
([name, version]) => `${name}@${version}`
|
|
468
|
+
),
|
|
469
|
+
...Object.entries(selectedRelease.devDependencies || {}).map(
|
|
470
|
+
([name, version]) => `${name}@${version}`
|
|
471
|
+
)
|
|
472
|
+
];
|
|
473
|
+
let nextSteps = [];
|
|
474
|
+
if (typeof instrunctionsFilePath === "string") {
|
|
475
|
+
let instructions = `Upgrade instructions created at:
|
|
476
|
+
file://${instrunctionsFilePath}`;
|
|
477
|
+
nextSteps.push(instructions);
|
|
478
|
+
}
|
|
479
|
+
const releaseNotesUrl = `https://hydrogen.shopify.dev/releases/${selectedRelease.version}`;
|
|
480
|
+
nextSteps.push(`Release notes:
|
|
481
|
+
${releaseNotesUrl}`);
|
|
482
|
+
const currentPinnedVersion = getAbsoluteVersion(currentVersion);
|
|
483
|
+
const selectedPinnedVersion = getAbsoluteVersion(selectedRelease.version);
|
|
484
|
+
const upgradedDependenciesOnly = currentPinnedVersion === selectedPinnedVersion;
|
|
485
|
+
const fromToMsg = `${currentPinnedVersion} \u2192 ${selectedPinnedVersion}`;
|
|
486
|
+
const headline = upgradedDependenciesOnly ? `You've have upgraded Hydrogen ${selectedPinnedVersion} dependencies` : `You've have upgraded from ${fromToMsg}`;
|
|
487
|
+
const packageManager = await getPackageManager(appPath);
|
|
488
|
+
return renderSuccess({
|
|
489
|
+
headline,
|
|
490
|
+
// @ts-ignore we know that filter(Boolean) will always return an array
|
|
491
|
+
customSections: [
|
|
492
|
+
{
|
|
493
|
+
title: "Updated dependencies",
|
|
494
|
+
body: [
|
|
495
|
+
{
|
|
496
|
+
list: {
|
|
497
|
+
items: updatedDependenciesList
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
title: "What\u2019s next?",
|
|
504
|
+
body: [
|
|
505
|
+
{
|
|
506
|
+
list: {
|
|
507
|
+
items: nextSteps
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
title: "Undo these upgrades?",
|
|
514
|
+
body: [
|
|
515
|
+
{
|
|
516
|
+
list: {
|
|
517
|
+
items: [
|
|
518
|
+
`Run \`git restore . && git clean -df && ${packageManager} i\``
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
].filter(Boolean)
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async function validateUpgrade({
|
|
528
|
+
appPath,
|
|
529
|
+
selectedRelease
|
|
530
|
+
}) {
|
|
531
|
+
const dependencies = await getDependencies(
|
|
532
|
+
path.join(appPath, "package.json")
|
|
533
|
+
);
|
|
534
|
+
const updatedVersion = dependencies["@shopify/hydrogen"];
|
|
535
|
+
if (!updatedVersion) {
|
|
536
|
+
throw new AbortError("Hydrogen version not found in package.json");
|
|
537
|
+
}
|
|
538
|
+
const updatedPinnedVersion = getAbsoluteVersion(updatedVersion);
|
|
539
|
+
if (updatedPinnedVersion !== selectedRelease.version) {
|
|
540
|
+
throw new AbortError(
|
|
541
|
+
`Failed to upgrade to Hydrogen version ${selectedRelease.version}`,
|
|
542
|
+
`You are still on version ${updatedPinnedVersion}`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function generateStepMd(item) {
|
|
547
|
+
const { steps } = item;
|
|
548
|
+
const heading = `### ${item.title} [#${item.id}](${item.pr})
|
|
549
|
+
`;
|
|
550
|
+
const body = steps?.map((step, stepIndex) => {
|
|
551
|
+
const pr = item.pr ? `[#${item.id}](${item.pr})
|
|
552
|
+
` : "";
|
|
553
|
+
const multiStep = steps.length > 1;
|
|
554
|
+
const title = multiStep ? `#### Step: ${stepIndex + 1}. ${step.title} ${pr}
|
|
555
|
+
` : `#### ${step.title.trim()}
|
|
556
|
+
`;
|
|
557
|
+
const info = step.info ? `> ${step.info}
|
|
558
|
+
` : "";
|
|
559
|
+
const code = step.code ? `${Buffer.from(step.code, "base64")}
|
|
560
|
+
` : "";
|
|
561
|
+
const docs = item.docs ? `[docs](${item.docs})
|
|
562
|
+
` : "";
|
|
563
|
+
return `${title}${info}${docs}${pr}${code}`;
|
|
564
|
+
}).join("\n");
|
|
565
|
+
return `${heading}
|
|
566
|
+
${body}`;
|
|
567
|
+
}
|
|
568
|
+
async function generateUpgradeInstructionsFile({
|
|
569
|
+
appPath,
|
|
570
|
+
cumulativeRelease,
|
|
571
|
+
currentVersion,
|
|
572
|
+
selectedRelease
|
|
573
|
+
}) {
|
|
574
|
+
let filename = "";
|
|
575
|
+
const { featuresMd, breakingChangesMd } = cumulativeRelease.features.filter((feature) => feature.steps).reduce(
|
|
576
|
+
(acc, feature) => {
|
|
577
|
+
if (feature.breaking) {
|
|
578
|
+
acc.breakingChangesMd.push(generateStepMd(feature));
|
|
579
|
+
} else {
|
|
580
|
+
acc.featuresMd.push(generateStepMd(feature));
|
|
581
|
+
}
|
|
582
|
+
return acc;
|
|
583
|
+
},
|
|
584
|
+
{ featuresMd: [], breakingChangesMd: [] }
|
|
585
|
+
);
|
|
586
|
+
const fixesMd = cumulativeRelease.fixes.filter((fixes) => fixes.steps).map(generateStepMd);
|
|
587
|
+
if (!featuresMd.length && !fixesMd.length) {
|
|
588
|
+
renderInfo({
|
|
589
|
+
headline: `No upgrade instructions generated`,
|
|
590
|
+
body: `There are no additional upgrade instructions for this version.`
|
|
591
|
+
});
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const absoluteFrom = getAbsoluteVersion(currentVersion);
|
|
595
|
+
const absoluteTo = getAbsoluteVersion(selectedRelease.version);
|
|
596
|
+
filename = `upgrade-${absoluteFrom}-to-${absoluteTo}.md`;
|
|
597
|
+
const instructionsFolderPath = path.join(appPath, INSTRUCTIONS_FOLDER);
|
|
598
|
+
const h1 = `# Hydrogen upgrade guide: ${absoluteFrom} to ${absoluteTo}`;
|
|
599
|
+
let md = `${h1}
|
|
600
|
+
|
|
601
|
+
----
|
|
602
|
+
`;
|
|
603
|
+
if (breakingChangesMd.length) {
|
|
604
|
+
md += `
|
|
605
|
+
## Breaking changes
|
|
606
|
+
|
|
607
|
+
${breakingChangesMd.join("\n")}
|
|
608
|
+
----
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
if (featuresMd.length) {
|
|
612
|
+
md += `
|
|
613
|
+
## Features
|
|
614
|
+
|
|
615
|
+
${featuresMd.join("\n")}
|
|
616
|
+
----
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
if (fixesMd.length) {
|
|
620
|
+
md += `
|
|
621
|
+
${featuresMd.length ? "----\n\n" : ""}## Fixes
|
|
622
|
+
|
|
623
|
+
${fixesMd.join(
|
|
624
|
+
"\n"
|
|
625
|
+
)}`;
|
|
626
|
+
}
|
|
627
|
+
const filePath = path.join(instructionsFolderPath, filename);
|
|
628
|
+
try {
|
|
629
|
+
await isDirectory(instructionsFolderPath);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
await mkdir(instructionsFolderPath);
|
|
632
|
+
}
|
|
633
|
+
if (!await fileExists(filePath)) {
|
|
634
|
+
await touchFile(filePath);
|
|
635
|
+
} else {
|
|
636
|
+
const overwriteMdFile = await renderConfirmationPrompt({
|
|
637
|
+
message: `A previous upgrade instructions file already exists for this version.
|
|
638
|
+
Do you want to overwrite it?`,
|
|
639
|
+
defaultValue: false
|
|
640
|
+
});
|
|
641
|
+
if (overwriteMdFile) {
|
|
642
|
+
await removeFile(`${filePath}.old`);
|
|
643
|
+
} else {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
await touchFile(filePath);
|
|
647
|
+
}
|
|
648
|
+
await writeFile(filePath, md);
|
|
649
|
+
return `${INSTRUCTIONS_FOLDER}/${filename}`;
|
|
650
|
+
}
|
|
651
|
+
async function displayDevUpgradeNotice({
|
|
652
|
+
targetPath
|
|
653
|
+
}) {
|
|
654
|
+
const appPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
655
|
+
const { currentVersion } = await getHydrogenVersion({ appPath });
|
|
656
|
+
const isPrerelease = semver.prerelease(currentVersion);
|
|
657
|
+
if (isPrerelease) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const changelog = await getChangelog();
|
|
661
|
+
const { availableUpgrades, uniqueAvailableUpgrades } = getAvailableUpgrades({
|
|
662
|
+
releases: changelog.releases,
|
|
663
|
+
currentVersion
|
|
664
|
+
});
|
|
665
|
+
if (availableUpgrades.length === 0 || !availableUpgrades[0]?.version) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const pinnedLatestVersion = getAbsoluteVersion(availableUpgrades[0].version);
|
|
669
|
+
const pinnedCurrentVersion = getAbsoluteVersion(currentVersion);
|
|
670
|
+
const currentReleaseIndex = changelog.releases.findIndex((release) => {
|
|
671
|
+
const pinnedReleaseVersion = getAbsoluteVersion(release.version);
|
|
672
|
+
return pinnedReleaseVersion === pinnedCurrentVersion;
|
|
673
|
+
});
|
|
674
|
+
const uniqueNextReleases = changelog.releases.slice(0, currentReleaseIndex).reverse().reduce((acc, release) => {
|
|
675
|
+
if (acc[release.version])
|
|
676
|
+
return acc;
|
|
677
|
+
acc[release.version] = release;
|
|
678
|
+
return acc;
|
|
679
|
+
}, {});
|
|
680
|
+
const nextReleases = Object.keys(uniqueNextReleases).length ? Object.entries(uniqueNextReleases).map(([version, release]) => {
|
|
681
|
+
return `${version} - ${release.title}`;
|
|
682
|
+
}).slice(0, 5) : [];
|
|
683
|
+
let headline = Object.keys(uniqueAvailableUpgrades).length > 1 ? `There are ${Object.keys(uniqueAvailableUpgrades).length} new @shopify/hydrogen versions available.` : `There's a new @shopify/hydrogen version available.`;
|
|
684
|
+
renderInfo({
|
|
685
|
+
headline,
|
|
686
|
+
body: [`Current: ${currentVersion} | Latest: ${pinnedLatestVersion}`],
|
|
687
|
+
//@ts-ignore will always be an array
|
|
688
|
+
customSections: nextReleases.length ? [
|
|
689
|
+
{
|
|
690
|
+
title: `The next ${nextReleases.length} version(s) include`,
|
|
691
|
+
body: [
|
|
692
|
+
{
|
|
693
|
+
list: {
|
|
694
|
+
items: [
|
|
695
|
+
...nextReleases,
|
|
696
|
+
availableUpgrades.length > 5 && `...more`
|
|
697
|
+
].flat().filter(Boolean)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
].filter(Boolean)
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
title: "Next steps",
|
|
704
|
+
body: [
|
|
705
|
+
{
|
|
706
|
+
list: {
|
|
707
|
+
items: [
|
|
708
|
+
`Run \`h2 upgrade\` or \`h2 upgrade --version XXXX.X.XX\``,
|
|
709
|
+
,
|
|
710
|
+
`Read release notes at https://hydrogen.shopify.dev/releases`
|
|
711
|
+
]
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
] : []
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export { buildUpgradeCommandArgs, Upgrade as default, displayConfirmation, displayDevUpgradeNotice, getAbsoluteVersion, getAvailableUpgrades, getChangelog, getCummulativeRelease, getHydrogenVersion, getSelectedRelease, hasOutdatedDependencies, isUpgradeableRelease, runUpgrade, upgradeNodeModules };
|