@shopify/cli-hydrogen 5.0.2 → 5.1.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 +16 -2
- package/dist/commands/hydrogen/codegen-unstable.js +13 -24
- package/dist/commands/hydrogen/dev.js +45 -39
- package/dist/commands/hydrogen/env/list.js +25 -24
- package/dist/commands/hydrogen/env/list.test.js +46 -43
- package/dist/commands/hydrogen/env/pull.js +53 -25
- package/dist/commands/hydrogen/env/pull.test.js +123 -42
- package/dist/commands/hydrogen/generate/route.js +31 -132
- package/dist/commands/hydrogen/generate/route.test.js +34 -126
- package/dist/commands/hydrogen/init.js +46 -127
- package/dist/commands/hydrogen/init.test.js +352 -100
- package/dist/commands/hydrogen/link.js +70 -69
- package/dist/commands/hydrogen/link.test.js +72 -107
- package/dist/commands/hydrogen/list.js +22 -12
- package/dist/commands/hydrogen/list.test.js +51 -48
- package/dist/commands/hydrogen/login.js +31 -0
- package/dist/commands/hydrogen/logout.js +21 -0
- package/dist/commands/hydrogen/setup/css.js +79 -0
- package/dist/commands/hydrogen/setup/markets.js +53 -0
- package/dist/commands/hydrogen/setup.js +133 -0
- package/dist/commands/hydrogen/shortcut.js +2 -45
- package/dist/commands/hydrogen/shortcut.test.js +10 -37
- package/dist/generator-templates/assets/css-modules/package.json +6 -0
- package/dist/generator-templates/assets/postcss/package.json +10 -0
- package/dist/generator-templates/assets/postcss/postcss.config.js +8 -0
- package/dist/generator-templates/assets/tailwind/package.json +13 -0
- package/dist/generator-templates/assets/tailwind/postcss.config.js +10 -0
- package/dist/generator-templates/assets/tailwind/tailwind.config.js +8 -0
- package/dist/generator-templates/assets/tailwind/tailwind.css +3 -0
- package/dist/generator-templates/assets/vanilla-extract/package.json +9 -0
- package/dist/generator-templates/starter/.eslintignore +5 -0
- package/dist/generator-templates/starter/.eslintrc.js +18 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +1 -0
- package/dist/generator-templates/starter/README.md +40 -0
- package/dist/generator-templates/starter/app/components/Aside.tsx +47 -0
- package/dist/generator-templates/starter/app/components/Cart.tsx +340 -0
- package/dist/generator-templates/starter/app/components/Footer.tsx +99 -0
- package/dist/generator-templates/starter/app/components/Header.tsx +178 -0
- package/dist/generator-templates/starter/app/components/Layout.tsx +95 -0
- package/dist/generator-templates/starter/app/components/Search.tsx +480 -0
- package/dist/generator-templates/starter/app/entry.client.tsx +12 -0
- package/dist/generator-templates/starter/app/entry.server.tsx +33 -0
- package/dist/generator-templates/starter/app/root.tsx +270 -0
- package/dist/generator-templates/starter/app/routes/$.tsx +7 -0
- package/dist/generator-templates/{routes → starter/app/routes}/[robots.txt].tsx +47 -69
- package/dist/generator-templates/starter/app/routes/[sitemap.xml].tsx +174 -0
- package/dist/generator-templates/starter/app/routes/_index.tsx +145 -0
- package/dist/generator-templates/starter/app/routes/account.$.tsx +9 -0
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +563 -0
- package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +309 -0
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +196 -0
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +289 -0
- package/dist/generator-templates/starter/app/routes/account.tsx +203 -0
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +157 -0
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +143 -0
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +33 -0
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +124 -0
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +207 -0
- package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +136 -0
- package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +342 -0
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +88 -0
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +162 -0
- package/dist/generator-templates/starter/app/routes/blogs._index.tsx +94 -0
- package/dist/generator-templates/starter/app/routes/cart.tsx +104 -0
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +184 -0
- package/dist/generator-templates/starter/app/routes/collections._index.tsx +120 -0
- package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +57 -0
- package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +94 -0
- package/dist/generator-templates/starter/app/routes/policies._index.tsx +63 -0
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +418 -0
- package/dist/generator-templates/starter/app/routes/search.tsx +168 -0
- package/dist/generator-templates/starter/app/styles/app.css +473 -0
- package/dist/generator-templates/starter/app/styles/reset.css +129 -0
- package/dist/generator-templates/starter/app/utils.ts +46 -0
- package/dist/generator-templates/starter/package.json +43 -0
- package/dist/generator-templates/starter/public/favicon.svg +28 -0
- package/dist/generator-templates/starter/remix.config.js +26 -0
- package/dist/generator-templates/starter/remix.env.d.ts +39 -0
- package/dist/generator-templates/starter/server.ts +253 -0
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +1906 -0
- package/dist/generator-templates/starter/tsconfig.json +22 -0
- package/dist/lib/auth.js +123 -0
- package/dist/lib/auth.test.js +157 -0
- package/dist/lib/build.js +51 -0
- package/dist/lib/check-version.js +3 -3
- package/dist/lib/check-version.test.js +24 -0
- package/dist/lib/codegen.js +26 -17
- package/dist/lib/environment-variables.js +68 -0
- package/dist/lib/environment-variables.test.js +147 -0
- package/dist/lib/file.js +41 -0
- package/dist/lib/file.test.js +69 -0
- package/dist/lib/flags.js +39 -2
- package/dist/lib/format-code.js +26 -0
- package/dist/lib/gid.js +12 -0
- package/dist/lib/{graphql.test.js → gid.test.js} +1 -1
- package/dist/lib/graphql/admin/client.js +27 -0
- package/dist/lib/graphql/admin/client.test.js +51 -0
- package/dist/lib/graphql/admin/create-storefront.js +13 -15
- package/dist/lib/graphql/admin/create-storefront.test.js +64 -0
- package/dist/lib/graphql/admin/fetch-job.js +6 -15
- package/dist/lib/graphql/admin/link-storefront.js +7 -11
- package/dist/lib/graphql/admin/link-storefront.test.js +38 -0
- package/dist/lib/graphql/admin/list-environments.js +2 -2
- package/dist/lib/graphql/admin/list-environments.test.js +44 -0
- package/dist/lib/graphql/admin/list-storefronts.js +7 -11
- package/dist/lib/graphql/admin/list-storefronts.test.js +44 -0
- package/dist/lib/graphql/admin/pull-variables.js +3 -3
- package/dist/lib/graphql/admin/pull-variables.test.js +37 -0
- package/dist/lib/graphql/business-platform/user-account.js +83 -0
- package/dist/lib/graphql/business-platform/user-account.test.js +80 -0
- package/dist/lib/log.js +185 -9
- package/dist/lib/log.test.js +92 -0
- package/dist/lib/mini-oxygen.js +19 -9
- package/dist/lib/missing-routes.js +0 -2
- package/dist/lib/onboarding/common.js +456 -0
- package/dist/lib/onboarding/index.js +2 -0
- package/dist/lib/onboarding/local.js +229 -0
- package/dist/lib/onboarding/remote.js +89 -0
- package/dist/lib/remix-version-interop.js +5 -5
- package/dist/lib/remix-version-interop.test.js +11 -1
- package/dist/lib/render-errors.js +13 -11
- package/dist/lib/setups/css/assets.js +89 -0
- package/dist/lib/setups/css/css-modules.js +22 -0
- package/dist/lib/setups/css/index.js +44 -0
- package/dist/lib/setups/css/postcss.js +34 -0
- package/dist/lib/setups/css/replacers.js +137 -0
- package/dist/lib/setups/css/tailwind.js +54 -0
- package/dist/lib/setups/css/vanilla-extract.js +22 -0
- package/dist/lib/setups/i18n/domains.test.js +25 -0
- package/dist/lib/setups/i18n/index.js +46 -0
- package/dist/lib/setups/i18n/replacers.js +227 -0
- package/dist/lib/setups/i18n/subdomains.test.js +25 -0
- package/dist/lib/setups/i18n/subfolders.test.js +25 -0
- package/dist/lib/setups/i18n/templates/domains.js +14 -0
- package/dist/lib/setups/i18n/templates/domains.ts +25 -0
- package/dist/lib/setups/i18n/templates/subdomains.js +14 -0
- package/dist/lib/setups/i18n/templates/subdomains.ts +24 -0
- package/dist/lib/setups/i18n/templates/subfolders.js +14 -0
- package/dist/lib/setups/i18n/templates/subfolders.ts +28 -0
- package/dist/lib/setups/routes/generate.js +244 -0
- package/dist/lib/setups/routes/generate.test.js +313 -0
- package/dist/lib/shell.js +52 -5
- package/dist/lib/shell.test.js +42 -16
- package/dist/lib/shopify-config.js +23 -18
- package/dist/lib/shopify-config.test.js +63 -73
- package/dist/lib/template-downloader.js +9 -7
- package/dist/lib/transpile-ts.js +9 -29
- package/dist/virtual-routes/routes/index.jsx +40 -19
- package/oclif.manifest.json +710 -1
- package/package.json +16 -16
- package/dist/commands/hydrogen/build.d.ts +0 -23
- package/dist/commands/hydrogen/check.d.ts +0 -15
- package/dist/commands/hydrogen/codegen-unstable.d.ts +0 -15
- package/dist/commands/hydrogen/dev.d.ts +0 -21
- package/dist/commands/hydrogen/env/list.d.ts +0 -18
- package/dist/commands/hydrogen/env/pull.d.ts +0 -22
- package/dist/commands/hydrogen/g.d.ts +0 -10
- package/dist/commands/hydrogen/generate/route.d.ts +0 -32
- package/dist/commands/hydrogen/generate/route.test.d.ts +0 -1
- package/dist/commands/hydrogen/generate/routes.d.ts +0 -16
- package/dist/commands/hydrogen/init.d.ts +0 -24
- package/dist/commands/hydrogen/init.test.d.ts +0 -1
- package/dist/commands/hydrogen/link.d.ts +0 -23
- package/dist/commands/hydrogen/link.test.d.ts +0 -1
- package/dist/commands/hydrogen/list.d.ts +0 -21
- package/dist/commands/hydrogen/list.test.d.ts +0 -1
- package/dist/commands/hydrogen/preview.d.ts +0 -17
- package/dist/commands/hydrogen/shortcut.d.ts +0 -9
- package/dist/commands/hydrogen/shortcut.test.d.ts +0 -1
- package/dist/commands/hydrogen/unlink.d.ts +0 -16
- package/dist/commands/hydrogen/unlink.test.d.ts +0 -1
- package/dist/create-app.d.ts +0 -1
- package/dist/generator-templates/routes/[sitemap.xml].tsx +0 -235
- package/dist/generator-templates/routes/account/login.tsx +0 -103
- package/dist/generator-templates/routes/account/register.tsx +0 -103
- package/dist/generator-templates/routes/cart.tsx +0 -81
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +0 -104
- package/dist/generator-templates/routes/collections/index.tsx +0 -102
- package/dist/generator-templates/routes/graphiql.tsx +0 -10
- package/dist/generator-templates/routes/index.tsx +0 -40
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +0 -112
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +0 -140
- package/dist/generator-templates/routes/policies/index.tsx +0 -117
- package/dist/generator-templates/routes/products/$productHandle.tsx +0 -92
- package/dist/hooks/init.d.ts +0 -5
- package/dist/lib/admin-session.d.ts +0 -6
- package/dist/lib/admin-session.js +0 -16
- package/dist/lib/admin-session.test.d.ts +0 -1
- package/dist/lib/admin-session.test.js +0 -27
- package/dist/lib/admin-urls.d.ts +0 -8
- package/dist/lib/check-lockfile.d.ts +0 -3
- package/dist/lib/check-lockfile.test.d.ts +0 -1
- package/dist/lib/check-version.d.ts +0 -16
- package/dist/lib/check-version.test.d.ts +0 -1
- package/dist/lib/codegen.d.ts +0 -26
- package/dist/lib/combined-environment-variables.d.ts +0 -8
- package/dist/lib/combined-environment-variables.js +0 -57
- package/dist/lib/combined-environment-variables.test.d.ts +0 -1
- package/dist/lib/combined-environment-variables.test.js +0 -111
- package/dist/lib/config.d.ts +0 -20
- package/dist/lib/flags.d.ts +0 -27
- package/dist/lib/flags.test.d.ts +0 -1
- package/dist/lib/graphql/admin/create-storefront.d.ts +0 -17
- package/dist/lib/graphql/admin/fetch-job.d.ts +0 -23
- package/dist/lib/graphql/admin/link-storefront.d.ts +0 -14
- package/dist/lib/graphql/admin/list-environments.d.ts +0 -21
- package/dist/lib/graphql/admin/list-storefronts.d.ts +0 -25
- package/dist/lib/graphql/admin/pull-variables.d.ts +0 -21
- package/dist/lib/graphql.d.ts +0 -21
- package/dist/lib/graphql.js +0 -18
- package/dist/lib/graphql.test.d.ts +0 -1
- package/dist/lib/log.d.ts +0 -6
- package/dist/lib/mini-oxygen.d.ts +0 -22
- package/dist/lib/missing-routes.d.ts +0 -8
- package/dist/lib/missing-routes.test.d.ts +0 -1
- package/dist/lib/missing-storefronts.d.ts +0 -5
- package/dist/lib/missing-storefronts.js +0 -18
- package/dist/lib/process.d.ts +0 -6
- package/dist/lib/pull-environment-variables.d.ts +0 -20
- package/dist/lib/pull-environment-variables.js +0 -57
- package/dist/lib/pull-environment-variables.test.d.ts +0 -1
- package/dist/lib/pull-environment-variables.test.js +0 -174
- package/dist/lib/remix-version-interop.d.ts +0 -11
- package/dist/lib/remix-version-interop.test.d.ts +0 -1
- package/dist/lib/render-errors.d.ts +0 -16
- package/dist/lib/shell.d.ts +0 -11
- package/dist/lib/shell.test.d.ts +0 -1
- package/dist/lib/shop.d.ts +0 -7
- package/dist/lib/shop.js +0 -32
- package/dist/lib/shop.test.d.ts +0 -1
- package/dist/lib/shop.test.js +0 -78
- package/dist/lib/shopify-config.d.ts +0 -35
- package/dist/lib/shopify-config.test.d.ts +0 -1
- package/dist/lib/string.d.ts +0 -3
- package/dist/lib/string.test.d.ts +0 -1
- package/dist/lib/template-downloader.d.ts +0 -6
- package/dist/lib/transpile-ts.d.ts +0 -16
- package/dist/lib/user-errors.d.ts +0 -9
- package/dist/lib/user-errors.js +0 -11
- package/dist/lib/virtual-routes.d.ts +0 -7
- package/dist/lib/virtual-routes.test.d.ts +0 -1
- /package/dist/{commands/hydrogen/env/list.test.d.ts → lib/setups/css/common.js} +0 -0
- /package/dist/{commands/hydrogen/env/pull.test.d.ts → lib/setups/i18n/mock-i18n-types.js} +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type {LanguageCode, CountryCode} from '../mock-i18n-types.js';
|
|
2
|
+
|
|
3
|
+
export type I18nLocale = {
|
|
4
|
+
language: LanguageCode;
|
|
5
|
+
country: CountryCode;
|
|
6
|
+
pathPrefix: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function getLocaleFromRequest(request: Request): I18nLocale {
|
|
10
|
+
const url = new URL(request.url);
|
|
11
|
+
const firstPathPart = url.pathname.split('/')[1]?.toUpperCase() ?? '';
|
|
12
|
+
|
|
13
|
+
let pathPrefix = '';
|
|
14
|
+
let language: LanguageCode = 'EN';
|
|
15
|
+
let country: CountryCode = 'US';
|
|
16
|
+
|
|
17
|
+
if (/^[A-Z]{2}-[A-Z]{2}$/i.test(firstPathPart)) {
|
|
18
|
+
pathPrefix = '/' + firstPathPart;
|
|
19
|
+
[language, country] = firstPathPart.split('-') as [
|
|
20
|
+
LanguageCode,
|
|
21
|
+
CountryCode,
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {language, country, pathPrefix};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {getLocaleFromRequest};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { readdir } from 'fs/promises';
|
|
2
|
+
import { fileExists, mkdir, copyFile, readFile, writeFile } from '@shopify/cli-kit/node/fs';
|
|
3
|
+
import { joinPath, relativizePath, dirname, relativePath, resolvePath, basename } from '@shopify/cli-kit/node/path';
|
|
4
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
5
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
6
|
+
import { transpileFile } from '../../../lib/transpile-ts.js';
|
|
7
|
+
import { getCodeFormatOptions, formatCode } from '../../../lib/format-code.js';
|
|
8
|
+
import { getTemplateAppFile, GENERATOR_ROUTE_DIR, getStarterDir } from '../../../lib/build.js';
|
|
9
|
+
import { getV2Flags, convertTemplateToRemixVersion, convertRouteToV1 } from '../../../lib/remix-version-interop.js';
|
|
10
|
+
import { getRemixConfig } from '../../../lib/config.js';
|
|
11
|
+
import { findFileWithExtension } from '../../file.js';
|
|
12
|
+
|
|
13
|
+
const NO_LOCALE_PATTERNS = [/robots\.txt/];
|
|
14
|
+
const ROUTE_MAP = {
|
|
15
|
+
home: ["_index", "$"],
|
|
16
|
+
page: "pages*",
|
|
17
|
+
cart: "cart",
|
|
18
|
+
products: "products*",
|
|
19
|
+
collections: "collections*",
|
|
20
|
+
policies: "policies*",
|
|
21
|
+
blogs: "blogs*",
|
|
22
|
+
account: "account*",
|
|
23
|
+
search: ["search", "api.predictive-search"],
|
|
24
|
+
robots: "[robots.txt]",
|
|
25
|
+
sitemap: "[sitemap.xml]"
|
|
26
|
+
};
|
|
27
|
+
let allRouteTemplateFiles = [];
|
|
28
|
+
async function getResolvedRoutes(routeKeys = Object.keys(ROUTE_MAP)) {
|
|
29
|
+
if (allRouteTemplateFiles.length === 0) {
|
|
30
|
+
allRouteTemplateFiles = (await readdir(getTemplateAppFile(GENERATOR_ROUTE_DIR))).map((item) => item.replace(/\.tsx?$/, ""));
|
|
31
|
+
}
|
|
32
|
+
const routeGroups = {};
|
|
33
|
+
const resolvedRouteFiles = [];
|
|
34
|
+
for (const key of routeKeys) {
|
|
35
|
+
routeGroups[key] = [];
|
|
36
|
+
const value = ROUTE_MAP[key];
|
|
37
|
+
if (!value) {
|
|
38
|
+
throw new AbortError(
|
|
39
|
+
`No route found for ${key}. Try one of ${ALL_ROUTE_CHOICES.join()}.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const routes = Array.isArray(value) ? value : [value];
|
|
43
|
+
for (const route of routes) {
|
|
44
|
+
const routePrefix = route.replace("*", "");
|
|
45
|
+
routeGroups[key].push(
|
|
46
|
+
...allRouteTemplateFiles.filter((file) => file.startsWith(routePrefix))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
resolvedRouteFiles.push(...routeGroups[key]);
|
|
50
|
+
}
|
|
51
|
+
return { routeGroups, resolvedRouteFiles };
|
|
52
|
+
}
|
|
53
|
+
const ALL_ROUTE_CHOICES = [...Object.keys(ROUTE_MAP), "all"];
|
|
54
|
+
async function generateRoutes(options) {
|
|
55
|
+
const { routeGroups, resolvedRouteFiles } = options.routeName === "all" ? await getResolvedRoutes() : await getResolvedRoutes([options.routeName]);
|
|
56
|
+
const { rootDirectory, appDirectory, future, tsconfigPath } = await getRemixConfig(options.directory);
|
|
57
|
+
const routesArray = resolvedRouteFiles.flatMap(
|
|
58
|
+
(item) => GENERATOR_ROUTE_DIR + "/" + item
|
|
59
|
+
);
|
|
60
|
+
const v2Flags = await getV2Flags(rootDirectory, future);
|
|
61
|
+
const formatOptions = await getCodeFormatOptions(rootDirectory);
|
|
62
|
+
const localePrefix = await getLocalePrefix(
|
|
63
|
+
appDirectory,
|
|
64
|
+
options,
|
|
65
|
+
v2Flags.isV2RouteConvention
|
|
66
|
+
);
|
|
67
|
+
const typescript = options.typescript ?? !!tsconfigPath;
|
|
68
|
+
const transpilerOptions = typescript ? void 0 : await getJsTranspilerOptions(rootDirectory);
|
|
69
|
+
const routes = [];
|
|
70
|
+
for (const route of routesArray) {
|
|
71
|
+
routes.push(
|
|
72
|
+
await generateProjectFile(route, {
|
|
73
|
+
...options,
|
|
74
|
+
typescript,
|
|
75
|
+
localePrefix,
|
|
76
|
+
rootDirectory,
|
|
77
|
+
appDirectory,
|
|
78
|
+
formatOptions,
|
|
79
|
+
transpilerOptions,
|
|
80
|
+
v2Flags
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
routes,
|
|
86
|
+
routeGroups,
|
|
87
|
+
isTypescript: typescript,
|
|
88
|
+
transpilerOptions,
|
|
89
|
+
v2Flags,
|
|
90
|
+
formatOptions
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function getLocalePrefix(appDirectory, { localePrefix, routeName }, isV2RouteConvention = true) {
|
|
94
|
+
if (localePrefix)
|
|
95
|
+
return localePrefix;
|
|
96
|
+
if (localePrefix !== void 0 || routeName === "all")
|
|
97
|
+
return;
|
|
98
|
+
const existingFiles = await readdir(joinPath(appDirectory, "routes")).catch(
|
|
99
|
+
() => []
|
|
100
|
+
);
|
|
101
|
+
const homeRouteWithLocaleRE = isV2RouteConvention ? /^\(\$(\w+)\)\._index.[jt]sx?$/ : /^\(\$(\w+)\)$/;
|
|
102
|
+
const homeRouteWithLocale = existingFiles.find(
|
|
103
|
+
(file) => homeRouteWithLocaleRE.test(file)
|
|
104
|
+
);
|
|
105
|
+
if (homeRouteWithLocale) {
|
|
106
|
+
return homeRouteWithLocale.match(homeRouteWithLocaleRE)?.[1];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function generateProjectFile(routeFrom, {
|
|
110
|
+
rootDirectory,
|
|
111
|
+
appDirectory,
|
|
112
|
+
typescript,
|
|
113
|
+
force,
|
|
114
|
+
adapter,
|
|
115
|
+
templatesRoot = getStarterDir(),
|
|
116
|
+
transpilerOptions,
|
|
117
|
+
formatOptions,
|
|
118
|
+
localePrefix,
|
|
119
|
+
v2Flags = {},
|
|
120
|
+
signal
|
|
121
|
+
}) {
|
|
122
|
+
const extension = (routeFrom.match(/(\.[jt]sx?)$/) ?? [])[1] ?? ".tsx";
|
|
123
|
+
routeFrom = routeFrom.replace(extension, "");
|
|
124
|
+
const routeTemplatePath = getTemplateAppFile(
|
|
125
|
+
routeFrom + extension,
|
|
126
|
+
templatesRoot
|
|
127
|
+
);
|
|
128
|
+
const allFilesToGenerate = await findRouteDependencies(
|
|
129
|
+
routeTemplatePath,
|
|
130
|
+
getTemplateAppFile("", templatesRoot)
|
|
131
|
+
);
|
|
132
|
+
const routeDestinationPath = joinPath(
|
|
133
|
+
appDirectory,
|
|
134
|
+
getDestinationRoute(routeFrom, localePrefix, v2Flags) + (typescript ? extension : extension.replace(".ts", ".js"))
|
|
135
|
+
);
|
|
136
|
+
const result = {
|
|
137
|
+
operation: "created",
|
|
138
|
+
sourceRoute: routeFrom,
|
|
139
|
+
destinationRoute: relativizePath(routeDestinationPath, rootDirectory)
|
|
140
|
+
};
|
|
141
|
+
if (!force && await fileExists(routeDestinationPath)) {
|
|
142
|
+
const shouldOverwrite = await renderConfirmationPrompt({
|
|
143
|
+
message: `The file ${result.destinationRoute} already exists. Do you want to replace it?`,
|
|
144
|
+
defaultValue: false,
|
|
145
|
+
confirmationMessage: "Yes",
|
|
146
|
+
cancellationMessage: "No",
|
|
147
|
+
abortSignal: signal
|
|
148
|
+
});
|
|
149
|
+
if (!shouldOverwrite)
|
|
150
|
+
return { ...result, operation: "skipped" };
|
|
151
|
+
result.operation = "replaced";
|
|
152
|
+
}
|
|
153
|
+
for (const filePath of allFilesToGenerate) {
|
|
154
|
+
const isRoute = filePath.startsWith(GENERATOR_ROUTE_DIR + "/");
|
|
155
|
+
const destinationPath = isRoute ? routeDestinationPath : joinPath(
|
|
156
|
+
appDirectory,
|
|
157
|
+
filePath.replace(/\.ts(x?)$/, `.${typescript ? "ts$1" : "js$1"}`)
|
|
158
|
+
);
|
|
159
|
+
if (!await fileExists(dirname(destinationPath))) {
|
|
160
|
+
await mkdir(dirname(destinationPath));
|
|
161
|
+
}
|
|
162
|
+
if (!/\.[jt]sx?$/.test(filePath)) {
|
|
163
|
+
await copyFile(
|
|
164
|
+
getTemplateAppFile(filePath, templatesRoot),
|
|
165
|
+
destinationPath
|
|
166
|
+
);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
let templateContent = convertTemplateToRemixVersion(
|
|
170
|
+
await readFile(getTemplateAppFile(filePath, templatesRoot)),
|
|
171
|
+
v2Flags
|
|
172
|
+
);
|
|
173
|
+
if (!typescript) {
|
|
174
|
+
templateContent = transpileFile(templateContent, transpilerOptions);
|
|
175
|
+
}
|
|
176
|
+
if (adapter) {
|
|
177
|
+
templateContent = templateContent.replace(
|
|
178
|
+
/@shopify\/remix-oxygen/g,
|
|
179
|
+
adapter
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
templateContent = formatCode(
|
|
183
|
+
templateContent,
|
|
184
|
+
formatOptions,
|
|
185
|
+
destinationPath
|
|
186
|
+
);
|
|
187
|
+
await writeFile(destinationPath, templateContent);
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
function getDestinationRoute(routeFrom, localePrefix, v2Flags) {
|
|
192
|
+
const routePath = routeFrom.replace(GENERATOR_ROUTE_DIR + "/", "");
|
|
193
|
+
const filePrefix = localePrefix && !NO_LOCALE_PATTERNS.some((pattern) => pattern.test(routePath)) ? `($${localePrefix})` + (v2Flags.isV2RouteConvention ? "." : "/") : "";
|
|
194
|
+
return GENERATOR_ROUTE_DIR + "/" + filePrefix + (v2Flags.isV2RouteConvention ? routePath : convertRouteToV1(routePath));
|
|
195
|
+
}
|
|
196
|
+
async function findRouteDependencies(routeFilePath, appDirectory) {
|
|
197
|
+
const filesToCheck = /* @__PURE__ */ new Set([routeFilePath]);
|
|
198
|
+
const fileDependencies = /* @__PURE__ */ new Set([relativePath(appDirectory, routeFilePath)]);
|
|
199
|
+
for (const filePath of filesToCheck) {
|
|
200
|
+
const importMatches = (await readFile(filePath, { encoding: "utf8" })).matchAll(/^(import|export)\s+.*?\s+from\s+['"](.*?)['"];?$/gims);
|
|
201
|
+
for (let [, , match] of importMatches) {
|
|
202
|
+
if (!match || !/^(\.|~)/.test(match))
|
|
203
|
+
continue;
|
|
204
|
+
match = match.replace(
|
|
205
|
+
"~",
|
|
206
|
+
relativePath(dirname(filePath), appDirectory) || "."
|
|
207
|
+
);
|
|
208
|
+
const resolvedMatchPath = resolvePath(dirname(filePath), match);
|
|
209
|
+
const absoluteFilepath = (await findFileWithExtension(
|
|
210
|
+
dirname(resolvedMatchPath),
|
|
211
|
+
basename(resolvedMatchPath)
|
|
212
|
+
)).filepath || resolvedMatchPath;
|
|
213
|
+
if (!absoluteFilepath.includes(`/${GENERATOR_ROUTE_DIR}/`)) {
|
|
214
|
+
fileDependencies.add(relativePath(appDirectory, absoluteFilepath));
|
|
215
|
+
if (/\.[jt]sx?$/.test(absoluteFilepath)) {
|
|
216
|
+
filesToCheck.add(absoluteFilepath);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return [...fileDependencies];
|
|
222
|
+
}
|
|
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
|
+
async function renderRoutePrompt(options) {
|
|
235
|
+
const generateAll = await renderConfirmationPrompt({
|
|
236
|
+
message: "Scaffold all standard route files? " + Object.keys(ROUTE_MAP).join(", "),
|
|
237
|
+
confirmationMessage: "Yes",
|
|
238
|
+
cancellationMessage: "No",
|
|
239
|
+
...options
|
|
240
|
+
});
|
|
241
|
+
return generateAll ? "all" : [];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { ALL_ROUTE_CHOICES, generateProjectFile, generateRoutes, getResolvedRoutes, renderRoutePrompt };
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { describe, beforeEach, vi, it, expect } from 'vitest';
|
|
2
|
+
import { temporaryDirectoryTask } from 'tempy';
|
|
3
|
+
import { getResolvedRoutes, generateRoutes, generateProjectFile } from './generate.js';
|
|
4
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { fileExists, readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
|
|
6
|
+
import { joinPath, dirname } from '@shopify/cli-kit/node/path';
|
|
7
|
+
import { getTemplateAppFile } from '../../../lib/build.js';
|
|
8
|
+
import { getRemixConfig } from '../../../lib/config.js';
|
|
9
|
+
|
|
10
|
+
const readProjectFile = (dirs, fileBasename, ext = "tsx") => readFile(joinPath(dirs.appDirectory, `${fileBasename}.${ext}`));
|
|
11
|
+
describe("generate/route", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
vi.mock("@shopify/cli-kit/node/output");
|
|
15
|
+
vi.mock("@shopify/cli-kit/node/ui");
|
|
16
|
+
vi.mock("../../config.js", async () => ({ getRemixConfig: vi.fn() }));
|
|
17
|
+
});
|
|
18
|
+
describe("generateRoutes", () => {
|
|
19
|
+
it("generates all routes with correct configuration", async () => {
|
|
20
|
+
const { resolvedRouteFiles } = await getResolvedRoutes();
|
|
21
|
+
expect(
|
|
22
|
+
resolvedRouteFiles.find((item) => /account_?\.login/.test(item))
|
|
23
|
+
).toBeTruthy();
|
|
24
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
25
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
26
|
+
files: [
|
|
27
|
+
["jsconfig.json", JSON.stringify({ compilerOptions: { test: "js" } })],
|
|
28
|
+
[".prettierrc.json", JSON.stringify({ singleQuote: false })]
|
|
29
|
+
],
|
|
30
|
+
templates: resolvedRouteFiles.map(
|
|
31
|
+
(filepath) => ["routes/" + filepath + ".tsx", ""]
|
|
32
|
+
)
|
|
33
|
+
});
|
|
34
|
+
vi.mocked(getRemixConfig).mockResolvedValue(directories);
|
|
35
|
+
const result = await generateRoutes({
|
|
36
|
+
routeName: "all",
|
|
37
|
+
directory: directories.rootDirectory,
|
|
38
|
+
templatesRoot: directories.templatesRoot
|
|
39
|
+
});
|
|
40
|
+
expect(result).toMatchObject(
|
|
41
|
+
expect.objectContaining({
|
|
42
|
+
isTypescript: false,
|
|
43
|
+
transpilerOptions: { test: "js" },
|
|
44
|
+
formatOptions: { singleQuote: false },
|
|
45
|
+
routes: expect.any(Array)
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
expect(result.routes).toHaveLength(
|
|
49
|
+
Object.values(resolvedRouteFiles).length
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
it("figures out the locale if a home route already exists", async () => {
|
|
54
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
55
|
+
const route = "routes/pages.$handle";
|
|
56
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
57
|
+
files: [
|
|
58
|
+
["tsconfig.json", JSON.stringify({ compilerOptions: { test: "ts" } })],
|
|
59
|
+
["app/routes/($locale)._index.tsx", "export const test = true;"]
|
|
60
|
+
],
|
|
61
|
+
templates: [[route + ".tsx", `const str = "hello world"`]]
|
|
62
|
+
});
|
|
63
|
+
vi.mocked(getRemixConfig).mockResolvedValue({
|
|
64
|
+
...directories,
|
|
65
|
+
tsconfigPath: "somewhere",
|
|
66
|
+
future: {
|
|
67
|
+
v2_routeConvention: true
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const result = await generateRoutes({
|
|
71
|
+
routeName: ["page"],
|
|
72
|
+
directory: directories.rootDirectory,
|
|
73
|
+
templatesRoot: directories.templatesRoot
|
|
74
|
+
});
|
|
75
|
+
expect(result).toMatchObject(
|
|
76
|
+
expect.objectContaining({
|
|
77
|
+
isTypescript: true,
|
|
78
|
+
transpilerOptions: void 0,
|
|
79
|
+
routes: expect.any(Array),
|
|
80
|
+
formatOptions: expect.any(Object)
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
expect(result.routes).toHaveLength(1);
|
|
84
|
+
expect(result.routes[0]).toMatchObject({
|
|
85
|
+
destinationRoute: expect.stringContaining("($locale).pages.$handle")
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("generateProjectFile", () => {
|
|
91
|
+
it("generates a route file for Remix v1", async () => {
|
|
92
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
93
|
+
const route = "routes/pages.$handle";
|
|
94
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
95
|
+
files: [],
|
|
96
|
+
templates: [[route + ".tsx", `const str = "hello world"`]]
|
|
97
|
+
});
|
|
98
|
+
await generateProjectFile(route, {
|
|
99
|
+
...directories,
|
|
100
|
+
v2Flags: {
|
|
101
|
+
isV2RouteConvention: false
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
expect(
|
|
105
|
+
await readProjectFile(directories, route.replace(".", "/"), "jsx")
|
|
106
|
+
).toContain(`const str = 'hello world'`);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it("generates a route file for Remix v2", async () => {
|
|
110
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
111
|
+
const route = "routes/custom.path.$handle._index";
|
|
112
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
113
|
+
files: [],
|
|
114
|
+
templates: [[route + ".tsx", `const str = "hello world"`]]
|
|
115
|
+
});
|
|
116
|
+
await generateProjectFile(route, {
|
|
117
|
+
...directories,
|
|
118
|
+
v2Flags: { isV2RouteConvention: true }
|
|
119
|
+
});
|
|
120
|
+
expect(await readProjectFile(directories, route, "jsx")).toContain(
|
|
121
|
+
`const str = 'hello world'`
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it("generates route files with locale prefix", async () => {
|
|
126
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
127
|
+
const routeCode = `const str = 'hello world'`;
|
|
128
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
129
|
+
files: [],
|
|
130
|
+
templates: [
|
|
131
|
+
["routes/_index.tsx", routeCode],
|
|
132
|
+
["routes/pages.$handle.tsx", routeCode],
|
|
133
|
+
["routes/[robots.txt].tsx", routeCode],
|
|
134
|
+
["routes/[sitemap.xml].tsx", routeCode]
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
const localePrefix = "locale";
|
|
138
|
+
await generateProjectFile("routes/_index", {
|
|
139
|
+
...directories,
|
|
140
|
+
v2Flags: { isV2RouteConvention: true },
|
|
141
|
+
localePrefix,
|
|
142
|
+
typescript: true
|
|
143
|
+
});
|
|
144
|
+
await generateProjectFile("routes/pages.$handle", {
|
|
145
|
+
...directories,
|
|
146
|
+
v2Flags: { isV2RouteConvention: false },
|
|
147
|
+
localePrefix,
|
|
148
|
+
typescript: true
|
|
149
|
+
});
|
|
150
|
+
await generateProjectFile("routes/[sitemap.xml]", {
|
|
151
|
+
...directories,
|
|
152
|
+
v2Flags: { isV2RouteConvention: true },
|
|
153
|
+
localePrefix,
|
|
154
|
+
typescript: true
|
|
155
|
+
});
|
|
156
|
+
await generateProjectFile("routes/[robots.txt]", {
|
|
157
|
+
...directories,
|
|
158
|
+
v2Flags: { isV2RouteConvention: true },
|
|
159
|
+
localePrefix,
|
|
160
|
+
typescript: true
|
|
161
|
+
});
|
|
162
|
+
await expect(
|
|
163
|
+
readProjectFile(directories, `routes/($locale)._index`)
|
|
164
|
+
).resolves.toContain(routeCode);
|
|
165
|
+
await expect(
|
|
166
|
+
readProjectFile(directories, `routes/($locale).[sitemap.xml]`)
|
|
167
|
+
).resolves.toContain(routeCode);
|
|
168
|
+
await expect(
|
|
169
|
+
readProjectFile(directories, `routes/[robots.txt]`)
|
|
170
|
+
).resolves.toContain(routeCode);
|
|
171
|
+
await expect(
|
|
172
|
+
readProjectFile(directories, `routes/($locale)/pages/$handle`)
|
|
173
|
+
).resolves.toContain(routeCode);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
it("produces a typescript file when typescript argument is true", async () => {
|
|
177
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
178
|
+
const route = "routes/pages.$handle";
|
|
179
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
180
|
+
files: [],
|
|
181
|
+
templates: [[route + ".tsx", 'const str = "hello typescript"']]
|
|
182
|
+
});
|
|
183
|
+
await generateProjectFile(route, {
|
|
184
|
+
...directories,
|
|
185
|
+
typescript: true,
|
|
186
|
+
v2Flags: { isV2RouteConvention: true }
|
|
187
|
+
});
|
|
188
|
+
expect(await readProjectFile(directories, route)).toContain(
|
|
189
|
+
`const str = 'hello typescript'`
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
it("prompts the user if there the file already exists", async () => {
|
|
194
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
195
|
+
vi.mocked(renderConfirmationPrompt).mockImplementationOnce(
|
|
196
|
+
async () => true
|
|
197
|
+
);
|
|
198
|
+
const route = "routes/page.$handle";
|
|
199
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
200
|
+
files: [[`app/${route}.jsx`, 'const str = "I exist"']],
|
|
201
|
+
templates: [[route + ".tsx", 'const str = "hello world"']]
|
|
202
|
+
});
|
|
203
|
+
await generateProjectFile(route, {
|
|
204
|
+
...directories,
|
|
205
|
+
v2Flags: { isV2RouteConvention: true }
|
|
206
|
+
});
|
|
207
|
+
expect(renderConfirmationPrompt).toHaveBeenCalledWith(
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
message: expect.stringContaining("already exists")
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
it("does not prompt the user if the force property is true", async () => {
|
|
215
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
216
|
+
vi.mocked(renderConfirmationPrompt).mockImplementationOnce(
|
|
217
|
+
async () => true
|
|
218
|
+
);
|
|
219
|
+
const route = "routes/page.$pageHandle";
|
|
220
|
+
const directories = await createHydrogenFixture(tmpDir, {
|
|
221
|
+
files: [[`app/${route}.jsx`, 'const str = "I exist"']],
|
|
222
|
+
templates: [[route + ".tsx", 'const str = "hello world"']]
|
|
223
|
+
});
|
|
224
|
+
await generateProjectFile(route, {
|
|
225
|
+
...directories,
|
|
226
|
+
force: true
|
|
227
|
+
});
|
|
228
|
+
expect(renderConfirmationPrompt).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
it("generates all the route dependencies", async () => {
|
|
232
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
233
|
+
const templates = [
|
|
234
|
+
[
|
|
235
|
+
"routes/pages.$pageHandle.tsx",
|
|
236
|
+
`import Dep from 'some-node-dep';
|
|
237
|
+
import AnotherRoute from './AnotherRoute';
|
|
238
|
+
import Form from '~/components/Form';
|
|
239
|
+
import {
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
Button} from '../components/Button';
|
|
243
|
+
import {stuff} from '../utils';
|
|
244
|
+
import {serverOnly} from '../something.server';
|
|
245
|
+
import styles from '../styles/app.css';
|
|
246
|
+
export {Dep, AnotherRoute, Form, Button, stuff, serverOnly, styles};
|
|
247
|
+
`
|
|
248
|
+
],
|
|
249
|
+
[
|
|
250
|
+
"components/Form.tsx",
|
|
251
|
+
`import {Button} from './Button';
|
|
252
|
+
import {Text} from './Text';
|
|
253
|
+
export {Button, Text};
|
|
254
|
+
`
|
|
255
|
+
],
|
|
256
|
+
["components/Button.tsx", `export const Button = '';
|
|
257
|
+
`],
|
|
258
|
+
["components/Text.tsx", `export const Text = '';
|
|
259
|
+
`],
|
|
260
|
+
["utils/index.ts", `export {stuff} from './stuff';
|
|
261
|
+
`],
|
|
262
|
+
["utils/stuff.ts", `export const stuff = '';
|
|
263
|
+
`],
|
|
264
|
+
["something.server.ts", `export const serverOnly = '';
|
|
265
|
+
`],
|
|
266
|
+
["styles/app.css", `.red{color:red;}`]
|
|
267
|
+
];
|
|
268
|
+
const directories = await createHydrogenFixture(tmpDir, { templates });
|
|
269
|
+
vi.mocked(getRemixConfig).mockResolvedValue(directories);
|
|
270
|
+
await generateProjectFile("routes/pages.$pageHandle", {
|
|
271
|
+
...directories,
|
|
272
|
+
v2Flags: { isV2RouteConvention: true },
|
|
273
|
+
force: true
|
|
274
|
+
});
|
|
275
|
+
await Promise.all(
|
|
276
|
+
templates.map(async ([file, content]) => {
|
|
277
|
+
const actualFile = joinPath(
|
|
278
|
+
directories.appDirectory,
|
|
279
|
+
file.replace(".ts", ".js")
|
|
280
|
+
);
|
|
281
|
+
await expect(fileExists(actualFile)).resolves.toBeTruthy();
|
|
282
|
+
await expect(readFile(actualFile)).resolves.toEqual(
|
|
283
|
+
content.replace(/\{\n+/, "{")
|
|
284
|
+
);
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
async function createHydrogenFixture(directory, {
|
|
292
|
+
files = [],
|
|
293
|
+
templates = []
|
|
294
|
+
}) {
|
|
295
|
+
const projectDir = "project";
|
|
296
|
+
for (const item of files) {
|
|
297
|
+
const [filePath, fileContent] = item;
|
|
298
|
+
const fullFilePath = joinPath(directory, projectDir, filePath);
|
|
299
|
+
await mkdir(dirname(fullFilePath));
|
|
300
|
+
await writeFile(fullFilePath, fileContent);
|
|
301
|
+
}
|
|
302
|
+
for (const item of templates) {
|
|
303
|
+
const [filePath, fileContent] = item;
|
|
304
|
+
const fullFilePath = getTemplateAppFile(filePath, directory);
|
|
305
|
+
await mkdir(dirname(fullFilePath));
|
|
306
|
+
await writeFile(fullFilePath, fileContent);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
rootDirectory: joinPath(directory, projectDir),
|
|
310
|
+
appDirectory: joinPath(directory, projectDir, "app"),
|
|
311
|
+
templatesRoot: directory
|
|
312
|
+
};
|
|
313
|
+
}
|
package/dist/lib/shell.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
|
6
6
|
import { execAsync } from './process.js';
|
|
7
7
|
|
|
8
8
|
const ALIAS_NAME = "h2";
|
|
9
|
-
const isWindows = () =>
|
|
9
|
+
const isWindows = () => os.platform() === "win32";
|
|
10
10
|
const isGitBash = () => !!process.env.MINGW_PREFIX;
|
|
11
11
|
function resolveFromHome(filepath) {
|
|
12
12
|
if (filepath[0] === "~") {
|
|
@@ -107,15 +107,62 @@ async function hasCliAlias() {
|
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
async function
|
|
111
|
-
|
|
110
|
+
async function createPlatformShortcut() {
|
|
111
|
+
const shortcuts = isWindows() && !isGitBash() ? await createShortcutsForWindows() : await createShortcutsForUnix();
|
|
112
|
+
return shortcuts;
|
|
113
|
+
}
|
|
114
|
+
const BASH_ZSH_COMMAND = `
|
|
115
|
+
# Shopify Hydrogen alias to local projects
|
|
116
|
+
alias ${ALIAS_NAME}='$(npm prefix -s)/node_modules/.bin/shopify hydrogen'`;
|
|
117
|
+
const FISH_FUNCTION = `
|
|
118
|
+
function ${ALIAS_NAME} --wraps='shopify hydrogen' --description 'Shortcut for the Hydrogen CLI'
|
|
119
|
+
set npmPrefix (npm prefix -s)
|
|
120
|
+
$npmPrefix/node_modules/.bin/shopify hydrogen $argv
|
|
121
|
+
end
|
|
122
|
+
`;
|
|
123
|
+
async function createShortcutsForUnix() {
|
|
124
|
+
const shells = [];
|
|
125
|
+
if (await shellWriteAlias("zsh", ALIAS_NAME, BASH_ZSH_COMMAND)) {
|
|
126
|
+
shells.push("zsh");
|
|
127
|
+
}
|
|
128
|
+
if (await shellWriteAlias("bash", ALIAS_NAME, BASH_ZSH_COMMAND)) {
|
|
129
|
+
shells.push("bash");
|
|
130
|
+
}
|
|
131
|
+
if (await shellWriteAlias("fish", ALIAS_NAME, FISH_FUNCTION)) {
|
|
132
|
+
shells.push("fish");
|
|
133
|
+
}
|
|
134
|
+
return shells;
|
|
135
|
+
}
|
|
136
|
+
const PS_FUNCTION = `function Invoke-Local-H2 {$npmPrefix = npm prefix -s; Invoke-Expression "$npmPrefix\\node_modules\\.bin\\shopify.ps1 hydrogen $Args"}; Set-Alias -Name ${ALIAS_NAME} -Value Invoke-Local-H2`;
|
|
137
|
+
const PS_APPEND_PROFILE_COMMAND = `
|
|
138
|
+
if (!(Test-Path -Path $PROFILE)) {
|
|
139
|
+
New-Item -ItemType File -Path $PROFILE -Force
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$profileContent = Get-Content -Path $PROFILE
|
|
143
|
+
if (!$profileContent -or $profileContent -NotLike '*Invoke-Local-H2*') {
|
|
144
|
+
Add-Content -Path $PROFILE -Value '${PS_FUNCTION}'
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
async function createShortcutsForWindows() {
|
|
148
|
+
const shells = [];
|
|
149
|
+
if (await shellRunScript(PS_APPEND_PROFILE_COMMAND, "powershell.exe")) {
|
|
150
|
+
shells.push("PowerShell");
|
|
151
|
+
}
|
|
152
|
+
if (await shellRunScript(PS_APPEND_PROFILE_COMMAND, "pwsh.exe")) {
|
|
153
|
+
shells.push("PowerShell 7+");
|
|
154
|
+
}
|
|
155
|
+
return shells;
|
|
156
|
+
}
|
|
157
|
+
async function getCliCommand(directory = process.cwd(), forcePkgManager) {
|
|
158
|
+
if (!forcePkgManager && await hasCliAlias()) {
|
|
112
159
|
return ALIAS_NAME;
|
|
113
160
|
}
|
|
114
161
|
let cli = "npx";
|
|
115
|
-
const pkgManager = await getPackageManager(
|
|
162
|
+
const pkgManager = forcePkgManager ?? await getPackageManager(directory).catch(() => null);
|
|
116
163
|
if (pkgManager === "pnpm" || pkgManager === "yarn")
|
|
117
164
|
cli = pkgManager;
|
|
118
165
|
return `${cli} shopify hydrogen`;
|
|
119
166
|
}
|
|
120
167
|
|
|
121
|
-
export { ALIAS_NAME, getCliCommand, isGitBash, isWindows, shellRunScript, shellWriteAlias };
|
|
168
|
+
export { ALIAS_NAME, createPlatformShortcut, getCliCommand, isGitBash, isWindows, shellRunScript, shellWriteAlias };
|