-
Notifications
You must be signed in to change notification settings - Fork 294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate Route CLI Command #365
Conversation
We noticed that this PR either modifies or introduces usage of the Docs are available here. If unavoidable, we reccomend using an HTML sanitizer like DOMPurify to sanitize content before If you have any questions or are unsure about how to move forward with this, ping #help-appsec and we would |
We detected some changes in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks for all the extra stuff added!
In the demo, there was some concern about adding templates/skeleton
. How about we move it to generators/skeleton
instead? We just need to change a few paths and add it to turbo.json
.
I added a few additional templates (for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worked like a charm for the most part!
cart: '/cart', | ||
product: '/products/$productHandle', | ||
collection: '/collections/$collectionHandle', | ||
collections: '/collections/index', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that makes sense cause there is no collections
page 🤦♂️ will remove this from the options for now (adding templates will be a whole other thing).
page: '/page/$pageHandle', | ||
cart: '/cart', | ||
product: '/products/$productHandle', | ||
collection: '/collections/$collectionHandle', | ||
collections: '/collections/index', | ||
policies: '/policies/index', | ||
policy: '/policies/$policyHandle', | ||
robots: '/[robots.txt]', | ||
sitemap: '/[sitemap.xml]', | ||
account: ['/account/login', '/account/register'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's consider how and when we'd bundle these together, like you have with the account
route.
For instance, you've implemented both collection
and collections
, to scaffold the $handle pages and the index, respectively. What are the advantages and tradeoffs of doing both at once? To me, it makes sense to just do them both in one go. If they don't want the index page it's easy to delete. Same with page(s), product(s), and polic(y/ies).
Whatever we do, let's make sure that we're consistent with how singular vs. plural option names behave!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm okay with doing both at once and being consistent. It removes some potential confusion or mis-typing between collection
and collections
.
The logic was added for the account
option, so this is more of a product and UX question. @benjaminsehl, @DavidWittness, or @mynameisadamf, let me know any of you guys disagree, otherwise going to move ahead with @gfscott's suggestion.
name: 'resource', | ||
description: `The resource to generate a route for.`, | ||
required: true, | ||
options: RESOURCES, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand why call it a resource
here, but wondering if there's a more specific term that would make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, maybe route
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Route aligns much better with Hydrogen. I built a prototype for apps yesterday and chose route as well.
} | ||
|
||
// We format the template content with Prettier. | ||
// TODO use @shopify/cli-kit's format function once it supports TypeScript |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, would love to get to this. The built-in next-steps and links features in cli-kit would be a good chance to point people to the API resources that are most relevant to each route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gfscott this comment is specifically about cli-kit
s file module which has a file.format
method that I added ages ago, but needs a little fix before we can use it here. We can still access all of the goodies for outputting nice success messages though, and I'm hoping @mynameisadamf can help with this. If you have something specific/ or an example I can work from, happy to start there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried tophating but getting 🤷🏼
[MODULE_NOT_FOUND] require failed to load │
│ /Users/juanp.prieto/code-work/2023/h2/node_modules/@shopify/cli-hydrogen/d │
│ ist/commands/hydrogen/generate/route
@juanpprieto you aren't the first to report issues with |
akshually I needed to do |
"fs-extra": "^10.1.0", | ||
"prettier": "^2.8.2", | ||
"typescript": "^4.8.3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the packages in the CLI we are more strict about the version ranges because breaking changes shipped as minor versions is more usual in Javascript than in other languages where it's easier for developers to catch them. I'd make the call here based on how much you trust that's not going to happen for these dependencies.
|
||
const RESOURCES = Object.keys(ROUTE_MAP); | ||
|
||
export default class GenerateRoute extends Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend doing a bit of separation of concerns here and extract the business logic from the command. The motivations are two:
- Make the logic more reusable
- Being able to test it outside of the context of OCLIF.
It's in our roadmap to iterate on the @shopify/cli-kit
's APIs to be more conventional about this.
export default class GenerateRoute extends Command { | ||
static flags = { | ||
path: commonFlags.path, | ||
adapter: Flags.string({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish we had the ESLint rule here (it's coming), but I'd recommend having an environment variable for each of the flags.
{ | ||
name: 'resource', | ||
description: `The resource to generate a route for.`, | ||
required: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here regarding environment variables
const isTypescript = await file.exists( | ||
path.join(directory, 'tsconfig.json'), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend to have an in-memory representation of a Hydrogen project and have validators that run against it.
const hydrogenProject = await loadProject({from: directory})
await validateIsConfiguredForTypescript(hydrogenProject)
// or
await validate(hydrogenProject)
for (const item of [ | ||
...(Array.isArray(resourcePath) ? resourcePath : [resourcePath]), | ||
]) { | ||
runGenerate(item, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who awaits this?
}, | ||
) { | ||
const extension = typescript ? '.tsx' : '.jsx'; | ||
const distPath = new URL('../../../', import.meta.url).pathname; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const distDirectory = hydrogenProject.directories.dist
) { | ||
const extension = typescript ? '.tsx' : '.jsx'; | ||
const distPath = new URL('../../../', import.meta.url).pathname; | ||
const templatePath = path.join(distPath, 'templates', `${resource}.tsx`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const templatesDirectory = hydrogenProject.directories.dist.templates
{ | ||
type: 'select', | ||
name: 'value', | ||
message: `The file ${path.relative( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are relativizing twice. You can do path.relativize
:
path.relativize(destinationPath)
It'll relativize from the working directory or the root depending on the resulting ../../../../
prefix.
// to JavaScript. We try to read the project's jsconfig.json, but if it | ||
// doesn't exist, we use a default configuration. | ||
if (!typescript) { | ||
const config = (await file.exists(path.join(directory, 'jsconfig.json'))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A function that generates routes knowing how to set up a project for Typescript doesn't feel right:
const remixProject = loadRemixProject({fromDirectory: directory})
if (!remixProject.isSetupForTypescript()) {
remixProject.setupForTypescript()
}
lib: ['DOM', 'DOM.Iterable', 'ES2022'], | ||
isolatedModules: true, | ||
esModuleInterop: true, | ||
resolveJsonModule: true, | ||
target: 'ES2022', | ||
strict: true, | ||
allowJs: true, | ||
forceConsistentCasingInFileNames: true, | ||
skipLibCheck: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this content disconnected from the version of Remix the project is using? If not, I'd recommend obtaining it from the project and having a map between Remix version and TSConfig
Co-authored-by: Fran Dios <frankdiox@gmail.com>
TLDR: This PR adds a new command to the CLI package that generates a templated routes modules.
To test this PR run
npx shopify hydrogen generate route collection
from inside thehello-world
template.collection
can be replaced with any of the following:We will add more routes (and refine these existing ones) in follow up PRs. These exist in a new
skeleton
template inside of this repo. You can run this template locally for convenience when developing or refining the generated routes.When building and developing the CLI these files are copied to the cli's dist folder so that they are locally available when running the command.
An optional
--adaptor
flag exists to support projects that are not using the@shopify/remix-oxygen
adaptor (the default).Running the
generate route
command will extract the route template and put it in the user's local project. It will also format and compile it if they are using JavaScript and respects a local jsconfig and prettierconfig if either exists.We also prompt if the file already exists (allowing users to overwrite if desired) and this can be bypassed with a
--force
flag.