@shopify/cli-hydrogen 5.1.2 → 5.2.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 +4 -1
- package/dist/commands/hydrogen/dev.js +25 -17
- package/dist/commands/hydrogen/generate/route.test.js +0 -1
- package/dist/commands/hydrogen/init.js +6 -3
- package/dist/commands/hydrogen/init.test.js +2 -0
- package/dist/commands/hydrogen/preview.js +2 -2
- package/dist/commands/hydrogen/setup.js +3 -0
- package/dist/generator-templates/starter/app/components/Footer.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Header.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Search.tsx +3 -3
- package/dist/generator-templates/starter/app/entry.server.tsx +9 -1
- package/dist/generator-templates/starter/app/root.tsx +31 -5
- package/dist/generator-templates/starter/app/routes/$.tsx +4 -0
- package/dist/generator-templates/starter/app/routes/_index.tsx +6 -2
- package/dist/generator-templates/starter/app/routes/account.$.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +2 -7
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +7 -2
- package/dist/generator-templates/starter/app/routes/account.tsx +4 -3
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +6 -2
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +6 -2
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +2 -6
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/blogs._index.tsx +2 -3
- package/dist/generator-templates/starter/app/routes/cart.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +2 -3
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +23 -15
- package/dist/generator-templates/starter/app/routes/search.tsx +1 -2
- package/dist/generator-templates/starter/package.json +5 -5
- package/dist/generator-templates/starter/remix.config.js +1 -0
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +9 -9
- package/dist/generator-templates/starter/tsconfig.json +1 -0
- package/dist/lib/ast.js +9 -0
- package/dist/lib/check-version.test.js +1 -0
- package/dist/lib/codegen.js +17 -7
- package/dist/lib/environment-variables.js +15 -11
- package/dist/lib/file.js +1 -1
- package/dist/lib/find-port.js +9 -0
- package/dist/lib/flags.js +6 -5
- package/dist/lib/format-code.js +7 -4
- package/dist/lib/graphql/admin/client.js +18 -0
- package/dist/lib/graphql/admin/client.test.js +28 -3
- package/dist/lib/live-reload.js +62 -0
- package/dist/lib/log.js +6 -1
- package/dist/lib/mini-oxygen.js +28 -18
- package/dist/lib/missing-routes.js +17 -1
- package/dist/lib/onboarding/common.js +5 -0
- package/dist/lib/onboarding/local.js +21 -8
- package/dist/lib/remix-config.js +2 -0
- package/dist/lib/remix-version-check.test.js +1 -0
- package/dist/lib/setups/css/index.js +4 -2
- package/dist/lib/setups/css/replacers.js +7 -4
- package/dist/lib/setups/i18n/replacers.js +7 -5
- package/dist/lib/setups/routes/generate.js +15 -29
- package/dist/lib/setups/routes/generate.test.js +1 -3
- package/dist/lib/template-downloader.js +4 -0
- package/dist/lib/transpile-ts.js +5 -3
- package/dist/lib/virtual-routes.js +4 -1
- package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +29 -4
- package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +44 -10
- package/dist/virtual-routes/components/IconBanner.jsx +289 -44
- package/dist/virtual-routes/components/IconDiscord.jsx +18 -1
- package/dist/virtual-routes/components/IconError.jsx +58 -17
- package/dist/virtual-routes/components/IconGithub.jsx +20 -1
- package/dist/virtual-routes/components/IconTwitter.jsx +18 -1
- package/dist/virtual-routes/components/Layout.jsx +2 -1
- package/dist/virtual-routes/routes/index.jsx +199 -94
- package/dist/virtual-routes/virtual-root.jsx +62 -16
- package/oclif.manifest.json +3 -3
- package/package.json +8 -7
package/dist/lib/mini-oxygen.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { outputToken, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
|
|
2
2
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
|
-
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
3
|
+
import { readFile, fileExists } from '@shopify/cli-kit/node/fs';
|
|
4
4
|
import colors from '@shopify/cli-kit/node/colors';
|
|
5
5
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
6
|
+
import { startServer } from '@shopify/mini-oxygen';
|
|
7
|
+
import { DEFAULT_PORT } from './flags.js';
|
|
6
8
|
|
|
7
9
|
async function startMiniOxygen({
|
|
8
10
|
root,
|
|
9
|
-
port =
|
|
11
|
+
port = DEFAULT_PORT,
|
|
10
12
|
watch = false,
|
|
13
|
+
autoReload = watch,
|
|
11
14
|
buildPathWorkerFile,
|
|
12
15
|
buildPathClient,
|
|
13
16
|
env
|
|
14
17
|
}) {
|
|
15
|
-
const { default: miniOxygenImport } = await import('@shopify/mini-oxygen');
|
|
16
|
-
const miniOxygenPreview = miniOxygenImport.default ?? miniOxygenImport;
|
|
17
18
|
const dotenvPath = resolvePath(root, ".env");
|
|
18
|
-
const miniOxygen = await
|
|
19
|
-
|
|
19
|
+
const miniOxygen = await startServer({
|
|
20
|
+
script: await readFile(buildPathWorkerFile),
|
|
20
21
|
assetsDir: buildPathClient,
|
|
21
22
|
publicPath: "",
|
|
22
23
|
port,
|
|
23
24
|
watch,
|
|
24
|
-
autoReload
|
|
25
|
+
autoReload,
|
|
25
26
|
modules: true,
|
|
26
27
|
env: {
|
|
27
28
|
...env,
|
|
@@ -30,23 +31,31 @@ async function startMiniOxygen({
|
|
|
30
31
|
envPath: !env && await fileExists(dotenvPath) ? dotenvPath : void 0,
|
|
31
32
|
log: () => {
|
|
32
33
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
onResponse: (request, response) => (
|
|
35
|
+
// 'Request' and 'Response' types in MiniOxygen comes from
|
|
36
|
+
// Miniflare and are slightly different from standard types.
|
|
37
|
+
logResponse(
|
|
38
|
+
request,
|
|
39
|
+
response
|
|
40
|
+
)
|
|
37
41
|
)
|
|
38
42
|
});
|
|
39
43
|
const listeningAt = `http://localhost:${miniOxygen.port}`;
|
|
40
44
|
return {
|
|
41
45
|
listeningAt,
|
|
42
46
|
port: miniOxygen.port,
|
|
43
|
-
reload(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
async reload(options = {}) {
|
|
48
|
+
const nextOptions = {};
|
|
49
|
+
if (options.env) {
|
|
50
|
+
nextOptions.env = {
|
|
51
|
+
...options.env,
|
|
47
52
|
...process.env
|
|
48
|
-
}
|
|
49
|
-
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (options.worker) {
|
|
56
|
+
nextOptions.script = await readFile(buildPathWorkerFile);
|
|
57
|
+
}
|
|
58
|
+
return miniOxygen.reload(nextOptions);
|
|
50
59
|
},
|
|
51
60
|
showBanner(options) {
|
|
52
61
|
console.log("");
|
|
@@ -64,8 +73,9 @@ async function startMiniOxygen({
|
|
|
64
73
|
function logResponse(request, response) {
|
|
65
74
|
try {
|
|
66
75
|
const url = new URL(request.url);
|
|
67
|
-
if (["/graphiql"].includes(url.pathname))
|
|
76
|
+
if (["/graphiql"].includes(url.pathname)) {
|
|
68
77
|
return;
|
|
78
|
+
}
|
|
69
79
|
const isProxy = !!response.url && response.url !== request.url;
|
|
70
80
|
const isDataRequest = !isProxy && url.searchParams.has("_data");
|
|
71
81
|
let route = request.url.replace(url.origin, "");
|
|
@@ -3,20 +3,34 @@ import { renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
|
3
3
|
const REQUIRED_ROUTES = [
|
|
4
4
|
"",
|
|
5
5
|
"cart",
|
|
6
|
+
// 'products',
|
|
6
7
|
"products/:productHandle",
|
|
7
8
|
"collections",
|
|
9
|
+
// 'collections/all',
|
|
8
10
|
"collections/:collectionHandle",
|
|
11
|
+
// 'collections/:collectionHandle/:constraint',
|
|
12
|
+
// 'collections/:collectionHandle/products/:productHandle',
|
|
9
13
|
"sitemap.xml",
|
|
10
14
|
"robots.txt",
|
|
11
15
|
"pages/:pageHandle",
|
|
16
|
+
// 'blogs/:blogHandle/tagged/:tagHandle',
|
|
17
|
+
// 'blogs/:blogHandle/:articleHandle',
|
|
18
|
+
// 'blogs/:blogHandle/:articleHandle/comments',
|
|
12
19
|
"policies/:policyHandle",
|
|
20
|
+
// 'variants/:variantId',
|
|
13
21
|
"search",
|
|
22
|
+
// 'gift_cards/:storeId/:cardId',
|
|
23
|
+
// 'discount/:discountCode', => Handled in storefrontRedirect
|
|
14
24
|
"account",
|
|
15
25
|
"account/login",
|
|
16
26
|
"account/register",
|
|
27
|
+
// 'account/addresses',
|
|
28
|
+
// 'account/orders',
|
|
17
29
|
"account/orders/:orderId",
|
|
18
30
|
"account/reset/:id/:token",
|
|
19
31
|
"account/activate/:id/:token"
|
|
32
|
+
// 'password',
|
|
33
|
+
// 'opening_soon',
|
|
20
34
|
];
|
|
21
35
|
function findMissingRoutes(config, requiredRoutes = REQUIRED_ROUTES) {
|
|
22
36
|
const userRoutes = Object.values(config.routes);
|
|
@@ -40,7 +54,9 @@ function findMissingRoutes(config, requiredRoutes = REQUIRED_ROUTES) {
|
|
|
40
54
|
currentRoute.parentId = parentRoute.parentId;
|
|
41
55
|
}
|
|
42
56
|
const optionalSegment = ":?[^\\/\\?]+\\?";
|
|
43
|
-
const reString = `^(${optionalSegment}\\/)?` +
|
|
57
|
+
const reString = `^(${optionalSegment}\\/)?` + // Starts with an optional segment
|
|
58
|
+
requiredRoute.replaceAll(".", "\\.").replace(/\//g, `\\/(${optionalSegment}\\/)?`).replace(/:[^/)?]+/g, ":[^\\/]+") + // Replace params with regex
|
|
59
|
+
`(\\/${optionalSegment})?$`;
|
|
44
60
|
if (new RegExp(reString).test(currentRoute.path)) {
|
|
45
61
|
missingRoutes.delete(requiredRoute);
|
|
46
62
|
}
|
|
@@ -163,7 +163,9 @@ async function handleProjectLocation({
|
|
|
163
163
|
return {
|
|
164
164
|
name: basename(location),
|
|
165
165
|
location,
|
|
166
|
+
// User input. E.g. "./hydrogen-storefront"
|
|
166
167
|
directory,
|
|
168
|
+
// Absolute path to location
|
|
167
169
|
storefrontTitle: storefrontInfo?.title
|
|
168
170
|
};
|
|
169
171
|
}
|
|
@@ -201,6 +203,7 @@ async function handleCssStrategy(projectDir, controller, flagStyling) {
|
|
|
201
203
|
{
|
|
202
204
|
rootDirectory: projectDir,
|
|
203
205
|
appDirectory: joinPath(projectDir, "app")
|
|
206
|
+
// Default value in new projects
|
|
204
207
|
},
|
|
205
208
|
true
|
|
206
209
|
);
|
|
@@ -344,6 +347,8 @@ async function renderProjectReady(project, {
|
|
|
344
347
|
body: bodyLines.map(
|
|
345
348
|
([label, value]) => ` ${(label + ":").padEnd(padMin, " ")} ${colors.dim(value)}`
|
|
346
349
|
).join("\n") + routeSummary,
|
|
350
|
+
// Use `customSections` instead of `nextSteps` and `references`
|
|
351
|
+
// here to enforce a newline between title and items.
|
|
347
352
|
customSections: [
|
|
348
353
|
hasErrors && {
|
|
349
354
|
title: "Warnings\n",
|
|
@@ -18,7 +18,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
18
18
|
message: "Connect to Shopify",
|
|
19
19
|
choices: [
|
|
20
20
|
{
|
|
21
|
-
label: "Use sample data from
|
|
21
|
+
label: "Use sample data from mock.shop (You can connect a Shopify account later)",
|
|
22
22
|
value: "mock"
|
|
23
23
|
},
|
|
24
24
|
{ label: "Link your Shopify account", value: "link" }
|
|
@@ -46,17 +46,22 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
46
46
|
let backgroundWorkPromise = copy(
|
|
47
47
|
templateDir,
|
|
48
48
|
project.directory,
|
|
49
|
+
// Filter out the `app` directory, which will be generated later
|
|
49
50
|
{
|
|
50
51
|
filter: (filepath) => !/^(app|dist|node_modules)\//i.test(
|
|
51
52
|
relativePath(templateDir, filepath)
|
|
52
53
|
)
|
|
53
54
|
}
|
|
54
55
|
).then(
|
|
55
|
-
() =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
() => (
|
|
57
|
+
// Generate project entries and their file dependencies
|
|
58
|
+
generateProjectEntries({
|
|
59
|
+
rootDirectory: project.directory,
|
|
60
|
+
appDirectory: joinPath(project.directory, "app"),
|
|
61
|
+
typescript: true
|
|
62
|
+
// Will be transpiled later
|
|
63
|
+
})
|
|
64
|
+
)
|
|
60
65
|
).catch(abort);
|
|
61
66
|
const tasks = [
|
|
62
67
|
{
|
|
@@ -74,6 +79,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
74
79
|
];
|
|
75
80
|
backgroundWorkPromise = backgroundWorkPromise.then(() => {
|
|
76
81
|
const promises = [
|
|
82
|
+
// Add project name to package.json
|
|
77
83
|
replaceFileContent(
|
|
78
84
|
joinPath(project.directory, "package.json"),
|
|
79
85
|
false,
|
|
@@ -83,17 +89,23 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
83
89
|
)
|
|
84
90
|
)
|
|
85
91
|
];
|
|
86
|
-
const envLeadingComment = "# The variables added in this file are only available locally in MiniOxygen
|
|
92
|
+
const envLeadingComment = "# The variables added in this file are only available locally in MiniOxygen.\n# Run `h2 link` to also inject environment variables from your storefront,\n# or `h2 env pull` to populate this file.";
|
|
87
93
|
if (storefrontInfo && createStorefrontPromise) {
|
|
88
94
|
promises.push(
|
|
95
|
+
// Save linked storefront in project
|
|
89
96
|
setUserAccount(project.directory, storefrontInfo),
|
|
90
97
|
createStorefrontPromise.then(
|
|
91
|
-
(storefront) =>
|
|
98
|
+
(storefront) => (
|
|
99
|
+
// Save linked storefront in project
|
|
100
|
+
setStorefront(project.directory, storefront)
|
|
101
|
+
)
|
|
92
102
|
),
|
|
103
|
+
// Write empty dotenv file to fallback to remote Oxygen variables
|
|
93
104
|
writeFile(joinPath(project.directory, ".env"), envLeadingComment)
|
|
94
105
|
);
|
|
95
106
|
} else if (templateAction === "mock") {
|
|
96
107
|
promises.push(
|
|
108
|
+
// Set required env vars
|
|
97
109
|
writeFile(
|
|
98
110
|
joinPath(project.directory, ".env"),
|
|
99
111
|
envLeadingComment + "\n" + [
|
|
@@ -188,6 +200,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
188
200
|
const { setupRoutes } = await handleRouteGeneration(
|
|
189
201
|
controller,
|
|
190
202
|
options.routes || true
|
|
203
|
+
// TODO: Remove default value when multi-select UI component is available
|
|
191
204
|
);
|
|
192
205
|
setupSummary.i18n = i18nStrategy;
|
|
193
206
|
backgroundWorkPromise = backgroundWorkPromise.then(async () => {
|
package/dist/lib/remix-config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { readdir } from 'node:fs/promises';
|
|
|
5
5
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
6
6
|
import { outputWarn } from '@shopify/cli-kit/node/output';
|
|
7
7
|
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
8
|
+
import { muteRemixLogs } from './log.js';
|
|
8
9
|
|
|
9
10
|
const BUILD_DIR = "dist";
|
|
10
11
|
const CLIENT_SUBDIR = "client";
|
|
@@ -25,6 +26,7 @@ function getProjectPaths(appPath, entry) {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
async function getRemixConfig(root, mode = process.env.NODE_ENV) {
|
|
29
|
+
await muteRemixLogs();
|
|
28
30
|
const { readConfig } = await import('@remix-run/dev/dist/config.js');
|
|
29
31
|
const config = await readConfig(root, mode);
|
|
30
32
|
if (process.env.LOCAL_DEV) {
|
|
@@ -26,6 +26,7 @@ describe("remix-version-check", () => {
|
|
|
26
26
|
it("warns when versions are out of sync", () => {
|
|
27
27
|
const expectedVersion = "42.0.0-test";
|
|
28
28
|
vi.mocked(requireMock).mockReturnValueOnce({
|
|
29
|
+
// Hydrogen expected version
|
|
29
30
|
dependencies: { "@remix-run/dev": expectedVersion }
|
|
30
31
|
});
|
|
31
32
|
const outputMock = mockAndCaptureOutput();
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { renderSelectPrompt } from '@shopify/cli-kit/node/ui';
|
|
2
|
+
import { SETUP_CSS_STRATEGIES } from './assets.js';
|
|
3
|
+
export { SETUP_CSS_STRATEGIES } from './assets.js';
|
|
2
4
|
import { setupTailwind } from './tailwind.js';
|
|
3
5
|
import { setupPostCss } from './postcss.js';
|
|
4
6
|
import { setupCssModules } from './css-modules.js';
|
|
5
7
|
import { setupVanillaExtract } from './vanilla-extract.js';
|
|
6
|
-
export { SETUP_CSS_STRATEGIES } from './assets.js';
|
|
7
8
|
|
|
9
|
+
const STYLING_CHOICES = [...SETUP_CSS_STRATEGIES, "none"];
|
|
8
10
|
const CSS_STRATEGY_NAME_MAP = {
|
|
9
11
|
tailwind: "Tailwind",
|
|
10
12
|
"css-modules": "CSS Modules",
|
|
@@ -41,4 +43,4 @@ async function renderCssPrompt(options) {
|
|
|
41
43
|
});
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
export { CSS_STRATEGY_NAME_MAP, renderCssPrompt, setupCssStrategy };
|
|
46
|
+
export { CSS_STRATEGY_NAME_MAP, STYLING_CHOICES, renderCssPrompt, setupCssStrategy };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
2
|
-
import { ts, tsx, js, jsx } from '@ast-grep/napi';
|
|
3
2
|
import { findFileWithExtension, replaceFileContent } from '../../file.js';
|
|
3
|
+
import { importLangAstGrep } from '../../ast.js';
|
|
4
4
|
|
|
5
|
-
const astGrep = { ts, tsx, js, jsx };
|
|
6
5
|
async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
7
6
|
const { filepath, astType } = await findFileWithExtension(
|
|
8
7
|
rootDirectory,
|
|
@@ -14,7 +13,8 @@ async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
|
14
13
|
);
|
|
15
14
|
}
|
|
16
15
|
await replaceFileContent(filepath, formatConfig, async (content) => {
|
|
17
|
-
const
|
|
16
|
+
const astGrep = await importLangAstGrep(astType);
|
|
17
|
+
const root = astGrep.parse(content).root();
|
|
18
18
|
const remixConfigNode = root.find({
|
|
19
19
|
rule: {
|
|
20
20
|
kind: "object",
|
|
@@ -22,9 +22,11 @@ async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
|
22
22
|
any: [
|
|
23
23
|
{
|
|
24
24
|
kind: "export_statement"
|
|
25
|
+
// ESM
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
kind: "assignment_expression",
|
|
29
|
+
// CJS
|
|
28
30
|
has: {
|
|
29
31
|
kind: "member_expression",
|
|
30
32
|
field: "left",
|
|
@@ -77,7 +79,8 @@ async function replaceRootLinks(appDirectory, formatConfig, importer) {
|
|
|
77
79
|
if (content.includes(importStatement.split("from")[0])) {
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
|
-
const
|
|
82
|
+
const astGrep = await importLangAstGrep(astType);
|
|
83
|
+
const root = astGrep.parse(content).root();
|
|
81
84
|
const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop();
|
|
82
85
|
const linksReturnNode = root.find({
|
|
83
86
|
utils: {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
2
2
|
import { joinPath, relativePath } from '@shopify/cli-kit/node/path';
|
|
3
3
|
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
4
|
-
import { ts, tsx, js, jsx } from '@ast-grep/napi';
|
|
5
4
|
import { replaceFileContent, findFileWithExtension } from '../../file.js';
|
|
5
|
+
import { importLangAstGrep } from '../../ast.js';
|
|
6
6
|
|
|
7
|
-
const astGrep = { ts, tsx, js, jsx };
|
|
8
7
|
async function replaceServerI18n({ rootDirectory, serverEntryPoint = "server" }, formatConfig, localeExtractImplementation) {
|
|
9
8
|
const { filepath, astType } = await findEntryFile({
|
|
10
9
|
rootDirectory,
|
|
11
10
|
serverEntryPoint
|
|
12
11
|
});
|
|
13
12
|
await replaceFileContent(filepath, formatConfig, async (content) => {
|
|
14
|
-
const
|
|
13
|
+
const astGrep = await importLangAstGrep(astType);
|
|
14
|
+
const root = astGrep.parse(content).root();
|
|
15
15
|
const requestIdentifier = root.find({
|
|
16
16
|
rule: {
|
|
17
17
|
kind: "identifier",
|
|
@@ -58,6 +58,7 @@ async function replaceServerI18n({ rootDirectory, serverEntryPoint = "server" },
|
|
|
58
58
|
has: {
|
|
59
59
|
kind: "identifier",
|
|
60
60
|
regex: `^${hydrogenImportName}`
|
|
61
|
+
// could be appended with " as ..."
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
});
|
|
@@ -154,10 +155,11 @@ async function replaceRemixEnv({ rootDirectory, serverEntryPoint }, formatConfig
|
|
|
154
155
|
rootDirectory,
|
|
155
156
|
entryFilepath
|
|
156
157
|
).replace(/.[tj]sx?$/, "");
|
|
157
|
-
await replaceFileContent(remixEnvPath, formatConfig, (content) => {
|
|
158
|
+
await replaceFileContent(remixEnvPath, formatConfig, async (content) => {
|
|
158
159
|
if (content.includes(`Storefront<`))
|
|
159
160
|
return;
|
|
160
|
-
const
|
|
161
|
+
const astGrep = await importLangAstGrep("ts");
|
|
162
|
+
const root = astGrep.parse(content).root();
|
|
161
163
|
const storefrontTypeNode = root.find({
|
|
162
164
|
rule: {
|
|
163
165
|
kind: "property_signature",
|
|
@@ -64,8 +64,7 @@ async function generateRoutes(options) {
|
|
|
64
64
|
options,
|
|
65
65
|
v2Flags.isV2RouteConvention
|
|
66
66
|
);
|
|
67
|
-
const typescript = options.typescript ??
|
|
68
|
-
const transpilerOptions = typescript ? void 0 : await getJsTranspilerOptions(rootDirectory);
|
|
67
|
+
const typescript = !!(options.typescript ?? tsconfigPath?.endsWith("tsconfig.json"));
|
|
69
68
|
const routes = [];
|
|
70
69
|
for (const route of routesArray) {
|
|
71
70
|
routes.push(
|
|
@@ -76,7 +75,6 @@ async function generateRoutes(options) {
|
|
|
76
75
|
rootDirectory,
|
|
77
76
|
appDirectory,
|
|
78
77
|
formatOptions,
|
|
79
|
-
transpilerOptions,
|
|
80
78
|
v2Flags
|
|
81
79
|
})
|
|
82
80
|
);
|
|
@@ -85,7 +83,6 @@ async function generateRoutes(options) {
|
|
|
85
83
|
routes,
|
|
86
84
|
routeGroups,
|
|
87
85
|
isTypescript: typescript,
|
|
88
|
-
transpilerOptions,
|
|
89
86
|
v2Flags,
|
|
90
87
|
formatOptions
|
|
91
88
|
};
|
|
@@ -98,12 +95,12 @@ async function getLocalePrefix(appDirectory, { localePrefix, routeName }, isV2Ro
|
|
|
98
95
|
const existingFiles = await readdir(joinPath(appDirectory, "routes")).catch(
|
|
99
96
|
() => []
|
|
100
97
|
);
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
(file) =>
|
|
98
|
+
const coreRouteWithLocaleRE = isV2RouteConvention ? /^\(\$(\w+)\)\.(_index|\$|cart).[jt]sx?$/ : /^\(\$(\w+)\)$/;
|
|
99
|
+
const coreRouteWithLocale = existingFiles.find(
|
|
100
|
+
(file) => coreRouteWithLocaleRE.test(file)
|
|
104
101
|
);
|
|
105
|
-
if (
|
|
106
|
-
return
|
|
102
|
+
if (coreRouteWithLocale) {
|
|
103
|
+
return coreRouteWithLocale.match(coreRouteWithLocaleRE)?.[1];
|
|
107
104
|
}
|
|
108
105
|
}
|
|
109
106
|
async function generateProjectFile(routeFrom, {
|
|
@@ -113,7 +110,6 @@ async function generateProjectFile(routeFrom, {
|
|
|
113
110
|
force,
|
|
114
111
|
adapter,
|
|
115
112
|
templatesRoot = getStarterDir(),
|
|
116
|
-
transpilerOptions,
|
|
117
113
|
formatOptions,
|
|
118
114
|
localePrefix,
|
|
119
115
|
v2Flags = {},
|
|
@@ -159,19 +155,17 @@ async function generateProjectFile(routeFrom, {
|
|
|
159
155
|
if (!await fileExists(dirname(destinationPath))) {
|
|
160
156
|
await mkdir(dirname(destinationPath));
|
|
161
157
|
}
|
|
158
|
+
const templateAppFilePath = getTemplateAppFile(filePath, templatesRoot);
|
|
162
159
|
if (!/\.[jt]sx?$/.test(filePath)) {
|
|
163
|
-
await copyFile(
|
|
164
|
-
getTemplateAppFile(filePath, templatesRoot),
|
|
165
|
-
destinationPath
|
|
166
|
-
);
|
|
160
|
+
await copyFile(templateAppFilePath, destinationPath);
|
|
167
161
|
continue;
|
|
168
162
|
}
|
|
169
163
|
let templateContent = convertTemplateToRemixVersion(
|
|
170
|
-
await readFile(
|
|
164
|
+
await readFile(templateAppFilePath),
|
|
171
165
|
v2Flags
|
|
172
166
|
);
|
|
173
167
|
if (!typescript) {
|
|
174
|
-
templateContent = transpileFile(templateContent
|
|
168
|
+
templateContent = await transpileFile(templateContent);
|
|
175
169
|
}
|
|
176
170
|
if (adapter) {
|
|
177
171
|
templateContent = templateContent.replace(
|
|
@@ -179,7 +173,7 @@ async function generateProjectFile(routeFrom, {
|
|
|
179
173
|
adapter
|
|
180
174
|
);
|
|
181
175
|
}
|
|
182
|
-
templateContent = formatCode(
|
|
176
|
+
templateContent = await formatCode(
|
|
183
177
|
templateContent,
|
|
184
178
|
formatOptions,
|
|
185
179
|
destinationPath
|
|
@@ -191,7 +185,9 @@ async function generateProjectFile(routeFrom, {
|
|
|
191
185
|
function getDestinationRoute(routeFrom, localePrefix, v2Flags) {
|
|
192
186
|
const routePath = routeFrom.replace(GENERATOR_ROUTE_DIR + "/", "");
|
|
193
187
|
const filePrefix = localePrefix && !NO_LOCALE_PATTERNS.some((pattern) => pattern.test(routePath)) ? `($${localePrefix})` + (v2Flags.isV2RouteConvention ? "." : "/") : "";
|
|
194
|
-
return GENERATOR_ROUTE_DIR + "/" + filePrefix +
|
|
188
|
+
return GENERATOR_ROUTE_DIR + "/" + filePrefix + // The template file uses the v2 route convention, so we need to convert
|
|
189
|
+
// it to v1 if the user is not using v2.
|
|
190
|
+
(v2Flags.isV2RouteConvention ? routePath : convertRouteToV1(routePath));
|
|
195
191
|
}
|
|
196
192
|
async function findRouteDependencies(routeFilePath, appDirectory) {
|
|
197
193
|
const filesToCheck = /* @__PURE__ */ new Set([routeFilePath]);
|
|
@@ -203,6 +199,7 @@ async function findRouteDependencies(routeFilePath, appDirectory) {
|
|
|
203
199
|
continue;
|
|
204
200
|
match = match.replace(
|
|
205
201
|
"~",
|
|
202
|
+
// import from '~/components/...'
|
|
206
203
|
relativePath(dirname(filePath), appDirectory) || "."
|
|
207
204
|
);
|
|
208
205
|
const resolvedMatchPath = resolvePath(dirname(filePath), match);
|
|
@@ -220,17 +217,6 @@ async function findRouteDependencies(routeFilePath, appDirectory) {
|
|
|
220
217
|
}
|
|
221
218
|
return [...fileDependencies];
|
|
222
219
|
}
|
|
223
|
-
async function getJsTranspilerOptions(rootDirectory) {
|
|
224
|
-
const jsConfigPath = joinPath(rootDirectory, "jsconfig.json");
|
|
225
|
-
if (!await fileExists(jsConfigPath))
|
|
226
|
-
return;
|
|
227
|
-
return JSON.parse(
|
|
228
|
-
(await readFile(jsConfigPath, { encoding: "utf8" })).replace(
|
|
229
|
-
/^\s*\/\/.*$/gm,
|
|
230
|
-
""
|
|
231
|
-
)
|
|
232
|
-
)?.compilerOptions;
|
|
233
|
-
}
|
|
234
220
|
async function renderRoutePrompt(options) {
|
|
235
221
|
const generateAll = await renderConfirmationPrompt({
|
|
236
222
|
message: "Scaffold all standard route files? " + Object.keys(ROUTE_MAP).join(", "),
|
|
@@ -39,7 +39,6 @@ describe("generate/route", () => {
|
|
|
39
39
|
expect(result).toMatchObject(
|
|
40
40
|
expect.objectContaining({
|
|
41
41
|
isTypescript: false,
|
|
42
|
-
transpilerOptions: { test: "js" },
|
|
43
42
|
formatOptions: { singleQuote: false },
|
|
44
43
|
routes: expect.any(Array)
|
|
45
44
|
})
|
|
@@ -61,7 +60,7 @@ describe("generate/route", () => {
|
|
|
61
60
|
});
|
|
62
61
|
vi.mocked(getRemixConfig).mockResolvedValue({
|
|
63
62
|
...directories,
|
|
64
|
-
tsconfigPath: "somewhere",
|
|
63
|
+
tsconfigPath: "somewhere/tsconfig.json",
|
|
65
64
|
future: {
|
|
66
65
|
v2_routeConvention: true
|
|
67
66
|
}
|
|
@@ -74,7 +73,6 @@ describe("generate/route", () => {
|
|
|
74
73
|
expect(result).toMatchObject(
|
|
75
74
|
expect.objectContaining({
|
|
76
75
|
isTypescript: true,
|
|
77
|
-
transpilerOptions: void 0,
|
|
78
76
|
routes: expect.any(Array),
|
|
79
77
|
formatOptions: expect.any(Object)
|
|
80
78
|
})
|
|
@@ -19,6 +19,7 @@ async function getLatestReleaseDownloadUrl(signal) {
|
|
|
19
19
|
}
|
|
20
20
|
const release = await response.json();
|
|
21
21
|
return {
|
|
22
|
+
// @shopify/package-name@version => package-name@version
|
|
22
23
|
version: release.name.split("/").pop() ?? release.name,
|
|
23
24
|
url: release.tarball_url
|
|
24
25
|
};
|
|
@@ -32,8 +33,11 @@ async function downloadTarball(url, storageDir, signal) {
|
|
|
32
33
|
);
|
|
33
34
|
}
|
|
34
35
|
await pipeline(
|
|
36
|
+
// Download
|
|
35
37
|
response.body,
|
|
38
|
+
// Decompress
|
|
36
39
|
gunzipMaybe(),
|
|
40
|
+
// Unpack
|
|
37
41
|
extract(storageDir, {
|
|
38
42
|
strip: 1,
|
|
39
43
|
filter: (name) => {
|
package/dist/lib/transpile-ts.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
-
import ts from 'typescript';
|
|
4
3
|
import glob from 'fast-glob';
|
|
5
4
|
import { outputDebug } from '@shopify/cli-kit/node/output';
|
|
6
5
|
import { getCodeFormatOptions, formatCode } from './format-code.js';
|
|
@@ -18,12 +17,15 @@ const DEFAULT_TS_CONFIG = {
|
|
|
18
17
|
forceConsistentCasingInFileNames: true,
|
|
19
18
|
skipLibCheck: true
|
|
20
19
|
};
|
|
21
|
-
function transpileFile(code, config = DEFAULT_TS_CONFIG) {
|
|
20
|
+
async function transpileFile(code, config = DEFAULT_TS_CONFIG) {
|
|
21
|
+
const tsImport = await import('typescript');
|
|
22
|
+
const ts = tsImport.default ?? tsImport;
|
|
22
23
|
const withArtificialNewLines = escapeNewLines(code);
|
|
23
24
|
const compiled = ts.transpileModule(withArtificialNewLines, {
|
|
24
25
|
reportDiagnostics: false,
|
|
25
26
|
compilerOptions: {
|
|
26
27
|
...config,
|
|
28
|
+
// '1' tells TypeScript to preserve the JSX syntax.
|
|
27
29
|
jsx: 1,
|
|
28
30
|
removeComments: false
|
|
29
31
|
}
|
|
@@ -81,7 +83,7 @@ async function transpileProject(projectDir) {
|
|
|
81
83
|
continue;
|
|
82
84
|
}
|
|
83
85
|
const tsx = await fs.readFile(entry, "utf8");
|
|
84
|
-
const mjs = formatCode(transpileFile(tsx), formatConfig);
|
|
86
|
+
const mjs = await formatCode(await transpileFile(tsx), formatConfig);
|
|
85
87
|
await fs.rm(entry);
|
|
86
88
|
await fs.writeFile(entry.replace(/\.ts(x?)$/, ".js$1"), mjs, "utf8");
|
|
87
89
|
}
|
|
@@ -12,7 +12,10 @@ async function addVirtualRoutes(config) {
|
|
|
12
12
|
const relativeFilePath = path.relative(virtualRoutesPath, absoluteFilePath);
|
|
13
13
|
const routePath = relativeFilePath.replace(/\.[jt]sx?$/, "").replaceAll("\\", "/");
|
|
14
14
|
const isIndex = /(^|\/)index$/.test(routePath);
|
|
15
|
-
const normalizedVirtualRoutePath = isIndex ? routePath.slice(0, -"index".length).replace(/\/$/, "") || void 0 :
|
|
15
|
+
const normalizedVirtualRoutePath = isIndex ? routePath.slice(0, -"index".length).replace(/\/$/, "") || void 0 : (
|
|
16
|
+
// TODO: support v2 flat routes?
|
|
17
|
+
routePath.replace(/\$/g, ":").replace(/[\[\]]/g, "")
|
|
18
|
+
);
|
|
16
19
|
const hasUserRoute = userRouteList.some(
|
|
17
20
|
(r) => r.parentId === "root" && r.path === normalizedVirtualRoutePath
|
|
18
21
|
);
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
const HydrogenLogoBaseBW = (props) => /* @__PURE__ */ jsxs(
|
|
3
|
+
"svg",
|
|
4
|
+
{
|
|
5
|
+
width: 81,
|
|
6
|
+
height: 82,
|
|
7
|
+
fill: "none",
|
|
8
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
9
|
+
...props,
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ jsx(
|
|
12
|
+
"path",
|
|
13
|
+
{
|
|
14
|
+
d: "M39.955 81.28 2.138 61.19l12.933-6.818 14.562 7.733 12.218-6.441L27.29 47.93l12.933-6.833L78.04 61.189l-12.934 6.817L51.35 60.7l-12.236 6.457 13.774 7.308-12.933 6.817Z",
|
|
15
|
+
fill: "#000"
|
|
16
|
+
}
|
|
17
|
+
),
|
|
18
|
+
/* @__PURE__ */ jsx(
|
|
19
|
+
"path",
|
|
20
|
+
{
|
|
21
|
+
fillRule: "evenodd",
|
|
22
|
+
clipRule: "evenodd",
|
|
23
|
+
d: "m40.225 0 39.953 21.227-15.073 7.945-13.756-7.308-10.096 5.328 13.775 7.309-15.075 7.945L0 21.22l15.073-7.945 14.562 7.732 10.078-5.313-14.56-7.731L40.225 0ZM29.426 7.967l14.564 7.734L29.63 23.27 15.07 15.537l-10.794 5.69 35.68 18.956 10.793-5.688-13.773-7.307L51.352 19.6l13.757 7.308 10.794-5.69-35.68-18.956-10.797 5.704Z",
|
|
24
|
+
fill: "#000"
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
);
|
|
5
30
|
export {
|
|
6
31
|
HydrogenLogoBaseBW
|
|
7
32
|
};
|