Create Custom CLIs - Live Workshop 2019-11-18 Part 2

Shawn Wang
InstructorShawn Wang
Share this video with your friends

Social Share Links

Send Tweet
Published 4 years ago
Updated 3 years ago

Create Custom CLIs with Shawn Wang

Instructor: [00:00] We're good to get going again. [00:06] I'm watching the chat. If you've [00:00] got any questions, if you just [00:00] need me to repeat some stuff, [00:00] feel free to reach out. If not, [00:00] we'll just keep going.

[00:20] We're [00:00] going to talk about debugging [00:00] and testing. It's just very [00:00] light. The number of things you [00:00] could possibly test for is [00:00] infinite. I chose to put this [00:00] earlier rather than later just [00:00] because that gives me some [00:00] leeway into showing you at least [00:00] something useful to get going.

[00:43] We're also going to start heavy [00:00] lifting. We're going to start [00:00] talking about enquirer using [00:00] userconfig, Scaffolding, and [00:00] Execa. This is the meat and [00:00] potatoes of the CLI course.

[00:58] Let's go back into ref...No, not [00:00] the references, debug testing.

" [01:08] How do you debug CLIs?"

[00:00] The [00:00] most generalized way for [00:00] anything Node is the inspect [00:00] flag. That's something that [00:00] everyone should at least try to [00:00] be familiar with.

[01:19] I'm not [00:00] super familiar with this myself. [00:00] Basically, you can link it up [00:00] with Chrome DevTools to do step- [00:00] through debugging.

[01:30] For [00:00] anything like, "Oh my god, I'm [00:00] so lost, and I don't know what's [00:00] going on," this is what I would [00:00] resort to. Unfortunately, I [00:00] don't run into this a lot, [00:00] because I'm fairly familiar with [00:00] CLIs now. This is, "Break glass [00:00] in case of emergency" type of [00:00] scenario. Use this.

[01:52] For oclif, [00:00] they care a lot about [00:00] performance, and they care about [00:00] making things show up when you [00:00] have errors that occur. Let's [00:00] see what errors occur when I do [00:00] a simple...I'll throw an error [00:00] here. I'll throw newError, and [00:00] this is what happens.

[02:20] Cli- [00:00] workshop dev. I'm sorry. The [00:00] lengths that I go to not type.

[02:33] I'm intentionally throwing an [00:00] error. It just gives me these [00:00] commands. To be honest, this [00:00] alone is enough for me to work [00:00] with. I can just click on this. [02:44] I put click on this, and it [00:00] takes me to an irrelevant flag, [00:00] but I can at least...

[02:53] Why [00:00] does it take me this to line six? [02:58] I'm not really sure why it takes [00:00] me to line six. It should just [00:00] take me to line 19, which maybe [00:00] is a source mapping issue. I [00:00] don't really know. We'll just [00:00] try and work through that.

[03:14] Anyway, this error isn't that [00:00] descriptive. We probably want [00:00] something more interesting. One [00:00] of the ways that we do that is [00:00] DEBUG=* and then we run the same [00:00] thing again.

[03:28] This taps into [00:00] the internal logging of oclif. [00:00] These logs are actually always [00:00] happening. They're just not [00:00] active at the point in time of a [00:00] regular run. This actually [00:00] answers the question of how do [00:00] we log things.

[03:45] Here we can [00:00] see oclif/config is where it's [00:00] reading the config, then it's [00:00] reading commands from our [00:00] directory, our CLI directory [00:00] directly, and then it's running [00:00] through them.

[00:00] You can see [00:00] each of these phase, how fast in [00:00] milliseconds each of them take. [04:04] It's really handy for [00:00] performance tuning. If I wanted [00:00] to look into this, typically, [00:00] something like 200, 300 [00:00] milliseconds is good.

[04:16] The [00:00] main hurdle that we have for [00:00] ourselves here is that we're [00:00] running TypeScript directly in [00:00] Node. Whereas in production, [00:00] we'd actually be building [00:00] through JS and then just running [00:00] raw JS. It should be faster in [00:00] production. It's just that in [00:00] development, we do tolerate [00:00] slower builds just for a nicer [00:00] developer experience just for [00:00] running directly from TypeScript.

[04:38] We're using TS Node under the [00:00] hood instead of TypeScript. We [00:00] find the error here. We proceed [00:00] to fix it and hopefully figure [00:00] it out.

[04:50] In this particular [00:00] instance, it's pointing us to [00:00] the wrong line. This is actually [00:00] rare. Usually, it's very [00:00] accurate as to which line it is. [05:01] I can't really explain this, but [00:00] most of the time, it's accurate [00:00] as to where the source of the [00:00] error is.

[05:07] I don't have [00:00] anything else to say from that [00:00] apart from that. Cool. That's [00:00] debugging.

[05:15] This comes from [00:00] oclif. What if you wanted to [00:00] insert some of your own logging [00:00] for your users? That's where you [00:00] start to come to use the debug [00:00] module. The debug module, you [00:00] can use very simply. I'm going [00:00] to install debug module down [00:00] over here.

[05:38] I have installed [00:00] it. Now I'm going to just [00:00] instrument my debug module [00:00] everywhere I go.

[05:45] It's going [00:00] to say debug dev, CLI workshop [00:00] dev command. It literally can be [00:00] any string that you care to [00:00] mention. Just treat this like a [00:00] regular console log. I can just [00:00] say, debug starting command [00:00] parsing.

[06:10] Over here, I'll say [00:00] parsing args and flags. Over [00:00] here, I can say parsing args and [00:00] flags are done. I can just log [00:00] out the args and flags.

[00:00] Over [00:00] here, I can say, done with [00:00] command.

[06:37] Basically, anything [00:00] that you would regularly console [00:00] long, you should put it in a [00:00] debug, because you may delete it [00:00] afterwards, but you may need it [00:00] in the future. You might as well [00:00] have a standard way of turning [00:00] it on and off. Debug is the tool [00:00] for that.

[06:51] Now, when I run [00:00] debug*CLI-workshop-dev over here, [00:00] it's going to log out both this [00:00] oclif core commands or the core [00:00] logs, as well as my logs. I can [00:00] see what's going on in each [00:00] parts of...Yeah, this thing. [07:14] There we go. File egg.

[00:00] I [00:00] defined egg somewhere? Yeah. [07:20] Here's my egg. This is really [00:00] cool.

[07:26] Obviously, it's super [00:00] noisy. You may not want all of [00:00] this. The way to do this is use [00:00] the globby, the thing that we [00:00] installed. Over here, I can say... [00:00] I prefix this as CLI workshop [00:00] dev. I can just say CLI workshop [00:00] star. That would do it. This [00:00] should only show me my stuff, [00:00] the stuff that I'm working on.

[07:50] Pretend you're that CLI user, [00:00] and you have users, and you want [00:00] them to debug for you. What you [00:00] would do is in your instructions, [00:00] you would tell them, "Hey, set [00:00] this environment variable, debug= [00:00] CL-workshop," whatever, and then [00:00] run the command that you'd [00:00] normally run. This will just [00:00] output your specific commands if [00:00] you think it's your code.

[08:14] If [00:00] you don't think it's your code, [00:00] then you can also do oclif and [00:00] then filter for only oclif- [00:00] specific questions. That's also [00:00] super interesting. It's @oclif. [08:36] Yeah. Yeah. See? Even for a [00:00] regular CLI debugging tool.

[08:44] This is even useful in the [00:00] browser, although you do have to [00:00] take care to not ship the wheel [00:00] of this in production. It can [00:00] actually be pretty helpful for [00:00] debugging in development as well. [00:00] Cool. Here's another example [00:00] code. You're free to try that [00:00] out on your own.

[09:08] I have a [00:00] question here from Will Johnson. " [09:10] What is the difference between [00:00] my logs and oclif logs?"

[09:15] Oclif logs are logged out for [00:00] you by the core framework. Your [00:00] logs are the ones that you asked [00:00] for. These are things that you [00:00] code, so it means something to [00:00] you in the context of your [00:00] business logic. That's about the [00:00] difference.

[09:34] This logs out [00:00] when you have that debug [00:00] environment variable. This logs [00:00] out no matter what? This.log. [09:42] This is oclif's default direct [00:00] console logging stuff. This is [00:00] the debug-only mode where you [00:00] only see it when you're [00:00] debugging. I hope that answers [00:00] your questions.

[10:00] Yes, oclif [00:00] uses debug under the hood. In [00:00] fact, you're going to find a lot [00:00] of your CLIs use debug under the [00:00] hood. You just learned something [00:00] that's extremely fundamental to [00:00] the Node.js ecosystem.

[10:13] "If [00:00] it's Gloves, can you or your log [00:00] with Oclif, assuming you have [00:00] multiple types of logs?"

[10:18] I [00:00] think you can. This gets into [00:00] the syntax of Globby. I don't [00:00] really memorize it. Most of the [00:00] times I don't need an or, I just [00:00] need a filter. I'm sure there's [00:00] like curly braces. Maybe you can [00:00] use an array.

[10:39] Where do you [00:00] find the syntax? There you go. [00:00] Yeah, or, you use curly braces. [00:00] Comma-separated list of or [00:00] expressions is the answer for [00:00] how to or stuff. There's a link [00:00] for that for more information [00:00] about how to use Globby.

[10:55] This [00:00] is something you'll find in all [00:00] CLIs. It's super, super common. [00:00] I don't even know how to find [00:00] this. O-R, that's great.

[11:05] What [00:00] else? Just things to match. [00:00] Obviously, this is super [00:00] theoretical, but if you [00:00] experiment enough, you'll find a [00:00] way to or stuff. Hopefully that [00:00] answers Zack's question.

[11:21] Koos [00:00] Kamara, you're totally right. [00:00] Oclif uses debug under hood. We [00:00] should use the same thing. [00:00] Everything presents in a nice [00:00] fashion. We can let the user [00:00] decide what they want to log out.

[11:34] One of my pet peeves is CLIs [00:00] that just talked too much. Just [00:00] stop talking. If I'm just asking [00:00] you to do a thing, just do the [00:00] thing. Only when there's [00:00] something wrong, then I turn you [00:00] on.

[11:46] There's this concept of [00:00] log levels, error log levels. [00:00] There's people have thought [00:00] through like one through nine [00:00] what log levels are. There's [00:00] five more rows of this. All [00:00] debug info warn error fatal off [00:00] and trace.

[12:04] The idea is that a [00:00] numerical level. This is zero, [00:00] one, two, three, four, five. [00:00] Then you're like, "All right, [00:00] log me everything less than five," [00:00] and then you get all these. [00:00] That's very broad, I think.

[12:20] I [00:00] much rather have just a Globby [00:00] thing that just says, "I want [00:00] everything that matches [00:00] something like an oclif," or, "I [00:00] just want everything that [00:00] matches my CLI workshop thing," [00:00] or, "If I using some other [00:00] library that uses debug, now I [00:00] want to just be able to narrow [00:00] down straight to that."

[12:38] This [00:00] will just have a lot of noise. [12:40] You'll just be like, "All right, [00:00] I'm at level five. OK, I'll have [00:00] to look at one, two, three, four [00:00] as well, when actually you just [00:00] want level five. This is a [00:00] superior method to log levels, [00:00] to be honest. Reasonable people [00:00] may disagree.

[12:55] That's logging. [00:00] It's simple. That is it. That is [00:00] the state of the art. It is so [00:00] good that you will...I cannot [00:00] imagine anything better and just [00:00] use it. That's good enough for [00:00] me.

[13:08] Testing. We're going to [00:00] try to use Jest because that is [00:00] the current favorite framework [00:00] for testing. Mocha is fine. [00:00] Mocha is what they use by [00:00] default. I just wanted to [00:00] challenge myself to try to [00:00] figure out how to use this in [00:00] Jest because that was [00:00] interesting.

[13:27] Wow, why are my...? [00:00] Jesus. Geez. My pathnames are [00:00] terrible. Anyway, I'm going to [00:00] install Jest Oclif test., and [00:00] then a bunch of TypeScript [00:00] dependencies. You're free to [00:00] ignore them if you don't need [00:00] them. I'm just installing Jest [00:00] for testing some of these CLS [00:00] that we've set up.

[13:54] We're also [00:00] going to need a jest.config. [00:00] While I'm waiting for that [00:00] installation to setup, I'm also [00:00] going to give a jest.config, so [00:00] jest.test. jest.config.js. I can [00:00] paste it in there. Obviously, [00:00] feel free to customize it [00:00] however the way you like. I [00:00] don't particularly have a strong [00:00] opinion on any of these.

[14:19] It [00:00] is the fact that, the less user- [00:00] facing your code is, the less [00:00] you're going to test it. I'll [00:00] come right out and say that I'm [00:00] not a super expert on this. I [00:00] want to make clear that it's [00:00] possible, and people do care [00:00] about it.

[14:37] For me, I move [00:00] things around very quickly, so [00:00] my tests would break very [00:00] quickly as well. The value isn't [00:00] that high. Obviously, for [00:00] anything production, that your [00:00] customers are relying on, [00:00] definitely test that. Especially [00:00] test in Windows and Linux as [00:00] well, because it's very easy to [00:00] code for Mac OSX and forget that [00:00] other operating systems exist.

[15:08] We've installed Jest [00:00] dependencies, and now we can [00:00] write some tests. I'm going to [00:00] create a test folder. I think [00:00] it's top-level test, jest. I [00:00] don't know why I named it like [00:00] that. Then, foo.ts.

[15:37] The test [00:00] itself is also going to be in [00:00] TypeScript. That's not a [00:00] necessity.

[15:43] Also, one thing I [00:00] wanted to point out is that, you [00:00] don't have to test the command [00:00] itself, you can just test core [00:00] logic. I have a function, I'll [00:00] import that, and I'll test it as [00:00] a unit test. It will obviously [00:00] be much faster than spinning up [00:00] oclif and simulating a command.

[16:01] Obviously, you get more [00:00] knowledge out of an integration [00:00] test. When you want to test, you [00:00] import the test from the oclif [00:00] test module, which you should [00:00] get by default as well. Maybe [00:00] not, I don't know. Did I install [00:00] it? Yeah, I did install it. [00:00] Fantastic. Wonderful, oclif.

[16:24] It's going to run dev, and then [00:00] it's going to pass a name, and [00:00] basically test the behavior, [00:00] like you would when you're using [00:00] it. Passing the command, and [00:00] then passing some flags or [00:00] arguments or whatever, and see [00:00] that the output is exactly what [00:00] you expect it to be.

[16:39] Here, I [00:00] think I'm going to run it, [00:00] because there's not much to say, [00:00] apart from, "Well, how do I run [00:00] this?" Actually, I tested this, [00:00] holy shit! I didn't include the [00:00] instructions for myself. Did I [00:00] have instructions here? No test? [17:02] That's not true.

[00:00] What happens [00:00] if I say yarn jest?

[17:14] My [00:00] computer's choking because of [00:00] the recording. "No tests found." [00:00] Fantastic. "8 files checked. No [00:00] tests found." Oh, I need to say [00:00] test.js.

[17:38] There we go. It's [00:00] running the test. It's [00:00] swallowing the standard out, [00:00] which is very nice. It's failing [00:00] over here. "Cannot use import [00:00] statement outside a module." I'm [00:00] not transforming this well [00:00] enough.

[18:07] Zach is saying, jest. [00:00] h. "It should have a help page [00:00] if it's a good CLI." That is [00:00] true. Jest is one of the highest [00:00] regarded CLIs in the world. I [00:00] have no doubt that it's...

[18:20] What do you mean dev is not [00:00] found? Dev should be found, [00:00] because we ran that down in here.

[18:43] I'm not really sure how to play [00:00] this. I think I've messed up in [00:00] one of my configs somewhere. [19:02] Where is the jest.config? Jest. [00:00] config is in the wrong place. Am [00:00] I in the right place? I should [00:00] be in the right place.

[00:00] What [00:00] is test.js? Let's delete that. [19:32] It's complaining to me about [00:00] TypeScript stuff. When in doubt, [00:00] just any everything.

[19:42] Anyway, [00:00] I know this is a sham of a test, [00:00] but we at least have something [00:00] showing up on screen. We'll be [00:00] happy it's running it. We have [00:00] issues with..."Redeclare block- [00:00] scoped test."

[20:03] What did I do? [00:00] Where did I redeclare? That's [00:00] not good. I have some conflicts [00:00] with that, so best...

[20:26] This is [00:00] definitely very off-pieced stuff. [00:00] Because I have conflicts between [00:00] TypeScript and Jest, this is [00:00] happening right now, which is [00:00] annoying.

[20:40] There we go! Yay! [00:00] We have tests breaking. That's [00:00] fantastic. Over here, we run dev, [00:00] and we expect to have Hello [00:00] World. Instead, we got Hello [00:00] World from .source/index.ts.

[00:00] In this case, the test is wrong. [00:00] Our implementation is the one [00:00] that we're coding to. I'm just [00:00] going to cheat and just type [00:00] that in there. That should be [00:00] finally a passing test.

[21:14] I [00:00] also sing. This may be a problem [00:00] for people.

[00:00] What? "Do I have [00:00] a to-be that's wrong?"

[00:00] Yeah. [21:35] I have an extra space. Goddammit. [00:00] I think I should trim this. Will [00:00] that help? I have an extra new [00:00] line. You see this new line over [00:00] here? That's super annoying. Oh [00:00] God.

[21:56] You could arguably trim [00:00] the new line in the [00:00] implementation, but I'd rather [00:00] just make the test more robust [00:00] because I may lose that new line [00:00] in the future.

[22:06] Ay, it's [00:00] running. Success. With any test, [00:00] we uncritically go on and...It's [00:00] good for a test to fail and then [00:00] you pass. It's too easy for a [00:00] bad test to just pass in the [00:00] first try.

[22:21] That's the rough [00:00] idea. You're straight up just [00:00] mocking and just running like [00:00] your user would use it. If [00:00] there's any file that you expect [00:00] to exist, then you have to write [00:00] assertions for that as well. [00:00] Other than that, that's pretty [00:00] much a straight-up Node test. [00:00] The only thing that's special is [00:00] this command. Fortunately, this [00:00] is something that oclif requires [00:00] for you.

[22:46] We actually write in [00:00] JavaScript in Netlify. I [00:00] actually haven't used this in [00:00] TypeScript yet. That's why we [00:00] have these clashes like this. [00:00] With enough time, I could figure [00:00] that out.

[22:59] Some more [00:00] principles. Recap of principles. [00:00] OSS when we know some Linux, [00:00] [inaudible] GitHub Actions may [00:00] be your best bet for doing this. [00:00] Use debug logging. They're the [00:00] best. They're awesome.

[00:00] Logging info is nice until [00:00] there's too much of it. Try to [00:00] let the user decide by offering [00:00] a regex or a global debug [00:00] solution. That's that. I'm [00:00] pretty happy with this. I can [00:00] move on with my head held high.

[23:28] Now we're into the heavy-lifting [00:00] section of the workshop. We're [00:00] going to talk about user input.

[23:40] I heard someone talk about [00:00] enquirer earlier. That is [00:00] absolutely the best well-known [00:00] user-prompting library.

[23:51] Unfortunately, I actually prefer [00:00] enquirer. That's smaller, faster, [00:00] and a whole bunch of other nice [00:00] attributes, but definitely feel [00:00] free to decide between both of [00:00] them. Just know that they both [00:00] exist.

[24:03] I particularly like [00:00] enquirer just because enquirer [00:00] is smaller. It also provides [00:00] some nicer UI features, which [00:00] we'll talk about. Let's see.

[24:15] Oclif UX itself also ships a [00:00] utility library. cli-ux. God, I [00:00] misnamed this so badly -- cli-ux. [24:27] Everyone has a prompt thing. [00:00] It's just an async function. You [00:00] just go wait CLI prompt. What is [00:00] your name? Then it waits for you [00:00] to respond, and then it fills [00:00] out your name variable.

[24:40] What [00:00] is your [inaudible]? It fills [00:00] your name variable, blah, blah, [00:00] blah. Blog standard stuff.

[24:46] This is fine. This is something [00:00] that you can use. It's there [00:00] available for you. I suggest [00:00] that you use enquirer, or at [00:00] least explore and know what it [00:00] does. Basically, it's just got a [00:00] lot of prompts that you're going [00:00] to want out of the box. A lot of [00:00] the others are going to miss [00:00] things in some way.

[25:07] In [00:00] particular, enquirer wants you [00:00] to use a plugin for autocomplete, [00:00] which isn't that nice to code up. [25:13] I've done it because I didn't [00:00] know enquirer existed. Then I [00:00] found enquirer, and I was like, " [00:00] Oh, this is first class. I'm [00:00] going to use that from now on."

[00:00] Here. That's why you're [00:00] benefiting from my burning [00:00] myself.

[25:29] Let's install [00:00] enquirer and see what it gives [00:00] us. Actually, let me just show [00:00] you the docs because my holy [00:00] shit, the docs.

[25:39] This is a [00:00] side project of some guy who [00:00] does this consultancy. Look at [00:00] this. Whoa. Survey prompts. [25:51] Let's do our nps in CLI. Why not? [00:00] How about let's do selections? [26:00] Let's do pick, pick, pick, up [00:00] down, up down.

[26:03] What's going [00:00] on here? You have colors. You [00:00] have key bindings. You have a [00:00] possible validation of the [00:00] results. There is a lot of [00:00] little nuances.

[26:17] Let me just... [00:00] Off, off. Here's username, [00:00] password hashing.

[26:23] Everything [00:00] you're used to on the front-end, [00:00] there is no date native DOM. You [00:00] just have to code from scratch [00:00] for yourself or use a library.

[26:31] This is something I care about, [00:00] autocomplete. I have a long list [00:00] of stuff. I just want the user [00:00] to fuzzy match against something [00:00] that they think that they [00:00] remember, and that's it.

[26:41] A [00:00] lot of other implementations [00:00] require exact match or for you [00:00] to scroll up and down the list. [26:47] That's silly because we have the [00:00] rest of the keyboard, so let's [00:00] just use autocomplete.

[26:54] What [00:00] else do I like? Confirm. Boring. [00:00] Form. This is interesting.

[00:00] Look at this. Nice accessible [00:00] form. You can go up and down. [27:05] Undo, undo. Redo. Then you get [00:00] an object at the end. Fantastic. [27:11] Use a form everywhere you can.

[00:00] Input prompts. Nice little [00:00] placeholder, animation, nice [00:00] cursor. Even the cursor you have [00:00] to code yourself because that's [00:00] not native in some instances.

[00:00] Invisible prompts. Let's say you [00:00] want to show something, like [00:00] your OTP password npm, a list [00:00] prompt.

[27:32] Then it will just [00:00] parse for you. That's a string [00:00] that's split, whatever. Anyway.

[27:37] I could go on and on, but we're [00:00] not going to use even half of [00:00] this today. We're just going to [00:00] explore how it looks.

[27:48] You [00:00] want to add enquirer. Again, [00:00] feel free to use npm, which is [00:00] [inaudible]. The difference is [00:00] small every day.

[28:00] I'm just [00:00] going to ignore that test, [00:00] because it's bothering me, but [00:00] definitely feel free to actually [00:00] use a test in a non-workshop [00:00] scenario.

[28:10] We're going to [00:00] upgrade our dev commands. [28:14] Instead of expecting a name, [00:00] what we're going to do is if [00:00] flags.name is undefined, then [00:00] we're going to prompt for a name.

[28:25] If flags.name...I'll just have [00:00] let name. If name is undefined, [00:00] then we're going to prompt for a [00:00] name.

[28:47] Here, we're going to [00:00] just use the basic prompt API of [00:00] enquirer. Where am I?

[28:56] There [00:00] are two ways to import prompts [00:00] for enquirer. There's the [00:00] generic prompts. This one you [00:00] can specify the type, or you can [00:00] specify capital I input and then [00:00] import prompts. Like capital I [00:00] input, and then just use that as [00:00] a class.

[29:13] I don't like that [00:00] because it's too finicky for [00:00] different input types, so I just [00:00] use the generic prompts, and I [00:00] use that everywhere I go. That's [00:00] a lot more flexible as far as [00:00] I'm concerned.

[29:25] Then we use [00:00] response here. We'll just say [00:00] name = response. Just assign [00:00] name. Do note that we're using [00:00] await here, so you have to in an [00:00] async block. That's fine because [00:00] oclif just does that for you by [00:00] default. I think we can run this. [29:44] Let's run it.

[00:00] We're going to [00:00] run this. I ran it without a [00:00] name flag. It's going to have [00:00] empty name. It's just going to [00:00] say, "What is your username?" [00:00] I'm going to say Ninja. Oh, and [00:00] it gives me an object. That is [00:00] unexpected.

[30:07] I should actually [00:00] destructure this. [inaudible] [00:00] response.

[30:19] It's going to be an [00:00] object with a name. You know [00:00] what, let's actually debug this [00:00] and just say username...Response, [00:00] I guess.

[30:35] Ah! What am I going [00:00] to do? Assign name equals to [00:00] response.username. There we go. [00:00] Fantastic. I got Ninja. Hello, [00:00] Ninja from Source.

[31:01] One of the [00:00] principles of CLI development is [00:00] to not over prompt too much. Now [00:00] we're starting to introduce [00:00] human-in-the-loop interaction.

[31:10] When I run a command, it may [00:00] actually not execute completely. [00:00] It may actually pause and expect [00:00] my response.

[31:18] This is bad for [00:00] CI. Your CLI will be used in CI [00:00] no matter whether you expect it [00:00] or not. This is something I [00:00] learned the hard way. You design [00:00] things to be used on your laptop [00:00] by a human and people just find [00:00] ways to use it inside of a [00:00] machine. Then they complain that [00:00] your prompting isn't helpful.

[31:44] The way that you should do this [00:00] is, you should do something like [00:00] a login feature like say, "In [00:00] the future, you can supply this [00:00] name via the name flag," like [00:00] that.

[32:03] This is a design [00:00] principle rather than a hard [00:00] rule, but every CLI should teach [00:00] you as you go along. Hey you can [00:00] supply the dash-dash name flag [00:00] and you don't have to supply [00:00] this prompt again.

[32:17] Dash-dash [00:00] name, Coca-Cola and then it just [00:00] runs without prompting. That's [00:00] an important...Whoa! Flags.name, [00:00] ivy.

[32:44] That's an important [00:00] design principle that you have a [00:00] way to escape prompting from [00:00] most of your commands where it [00:00] makes sense.

[32:52] Obviously, some [00:00] commands are definitely only run [00:00] by humans but most of them will [00:00] be run by CI. If you run a [00:00] prompt, your prompt should teach [00:00] the human how to run it without [00:00] the prompts. The prompt just [00:00] does away with itself. OK? [00:00] Entiendo? Bueno.

[33:15] There are [00:00] other options in here. We've [00:00] only used the three, main [00:00] required ones. The type of the [00:00] prompts, the name of the prompts, [00:00] so that's where the field comes [00:00] from, this user field, then the [00:00] message of the prompts which is [00:00] this very nice "What is your [00:00] username" thing over here.

[33:30] There's other options like [00:00] initial, what is the default [00:00] value, how do you format the [00:00] user input to show up on screen.

[33:36] They can type in one thing and [00:00] you can show another thing. It's [00:00] very fancy. Then you can format [00:00] the result or validate the [00:00] result. These are all standard [00:00] form input stuff as well.

[33:45] I [00:00] also like the autocomplete. I [00:00] love the autocomplete. Look at [00:00] this. Can't get enough. Let's [00:00] just swap it out.

[33:57] You can see [00:00] that there's other fields in [00:00] here. There's the limit field, [00:00] there's the choices field. [34:04] Basically every different type [00:00] of prompt will have their own [00:00] custom types and choices.

[00:00] Instead of flags, let's just [00:00] call it flags. Let's call it [00:00] name. I don't care. [inaudible] [00:00] flavor though. This name field [00:00] is important and that's how you [00:00] do multiple prompts as well.

[34:22] If you've given an array to this, [00:00] then they all have different [00:00] names and it would just be [00:00] attached on to the response [00:00] object. Let's just see how this [00:00] works. I need to run it without.

[34:44] See that substring matching, and [00:00] it's also case insensitive which [00:00] is very nice. Let's put [00:00] pineapple. You should always put [00:00] that on pizza. It's great.

[35:00] Most CLIs don't have this and I [00:00] feel like this should be default. [00:00] I have very strong opinions here. [00:00] Holly shit. Anyway, we've [00:00] explored how to put in user [00:00] input.

[35:13] I've shown you the [00:00] ropes as to adding other stuff. [00:00] You guys liked it.

[35:19] John [00:00] Claude says, "Fire." Will [00:00] Johnson says, "That was pretty [00:00] cool." Hempshtad says, "Cool." [00:00] That's cool.

[35:27] I'm glad you [00:00] like it because...I discovered [00:00] this a while ago and I don't [00:00] know. I wish more people had [00:00] this. It's such an easy win. Oh [00:00] my God.

[35:40] I'm installing one [00:00] library. It's pretty small. They [00:00] have some benchmarks as to how [00:00] much small it is compared to [00:00] enquirer. Then you're just doing [00:00] stuff like this and that's it.

[35:50] Let's do more. Let's do more. [00:00] We're doing user input. What [00:00] else can we do to feed in [00:00] information from the user to the [00:00] program? We've done flags. We've [00:00] done user input.

[36:04] There's one [00:00] other major source of input, [00:00] which is configs, which is like [00:00] babel.config, jest.config, [00:00] prettierrc, whatever.

[36:17] John [00:00] Clark has a question. "Why not [00:00] choose a different name, though?" [00:00] Naming is hard.

[00:00] Next question. " [00:00] Enquirer did come second." [00:00] Enquirer is the big dog, so you [00:00] fork it and then you call it [00:00] something similar. This is a [00:00] thing in JS. I don't have a [00:00] strong opinion there.

[36:42] I [00:00] should also mention. Some people [00:00] might not want to use enquirer [00:00] because of the author. The [00:00] author is Jon Schlinkert. I call [00:00] his universe the Schlinkertverse. [36:54] He is famous for doing a lot of [00:00] small modules. Let me show you.

[00:00] Just by installing his package, [00:00] you will install a whole bunch [00:00] of his other package like is- [00:00] number. The is-number package is [00:00] three lines of code. I guess [00:00] it's four lines, five lines of [00:00] codes.

[37:14] He's a reusable [00:00] modules guy. He'll do things [00:00] like this. People don't like it [00:00] because it's like a thousand [00:00] different packages.

[37:24] I don't [00:00] care. As long as long as it [00:00] gives me reliable software, I'm [00:00] fine with it. Obviously, [00:00] reasonable people disagree. I [00:00] view it as a high class problem [00:00] that we have this, real choices [00:00] as to what we can use for our [00:00] input libraries and CLIs. I'll [00:00] take what I can get.

[37:50] You [00:00] should be aware that Jon [00:00] Schlinkert has a reputation in [00:00] the Node community as someone [00:00] who uses his own modules of [00:00] everything a lot. I don't care [00:00] about GitHub stars or npm [00:00] downloads he does, whatever.

[38:07] Cosmiconfig. I already talked [00:00] about cli-config. If I were to [00:00] do this every damn time... [00:00] There's this which is which [00:00] gives me the prompts. That's [00:00] fine.

[00:00] It's annoying for...I [00:00] don't want to keep doing that. I [00:00] upgrade to this and I just keep [00:00] supplying this field. I end up [00:00] with this giant field of every [00:00] single config operation.

[38:32] Eventually, I want to have [00:00] something like a nice contains [00:00] config file, like a tsconfig. [00:00] json, or a jest.config.js. Very [00:00] nice.

[38:43] It's so funny. I'm [00:00] telling you how to write these [00:00] CLIs by showing you actual CLIs [00:00] that we're using to write these [00:00] CLIs, so meta, or oclif.

[00:00] What's it doing in package.json? [00:00] It's just a fact that they're [00:00] all these conventions. Everyone [00:00] has consolidated on these [00:00] conventions that something in a [00:00] JSON file is the same as a JS [00:00] file as something in a package. [00:00] json field is something as same [00:00] as the RC field and then you [00:00] have to recurse up the paras [00:00] that gives you as a mono repo.

[39:17] All this shit like that is [00:00] standardized. I don't want to [00:00] code any of it.

[39:26] One more [00:00] other conversation that we [00:00] should have is JSON is obviously [00:00] native to JavaScript. You have [00:00] JSON.stringify and JSON.parse. [39:35] It's very easy to serialize and [00:00] deserialize.

[00:00] You don't have [00:00] comments in that in. Maybe you [00:00] want to use a YAML file where [00:00] you can preserve comments. [00:00] That's nicer for developer [00:00] experience. YAML is also too [00:00] dynamic. It's a turing complete [00:00] language, so that's insecure.

[00:00] Maybe you want to use TOML which [00:00] is invented by Tom Preston- [00:00] Werner, the founder of GitHub, [00:00] or you want to try out the new [00:00] kid on the block, Golang, which [00:00] is getting a lot of attention as [00:00] a config language.

[40:01] This is a [00:00] highly-debated topic among [00:00] config experts. We're not there [00:00] yet. Pick one, let's just stick [00:00] with it.

[40:09] Cosmiconfig is the [00:00] default, so we're going to cover [00:00] it here, but obviously, this is [00:00] a whole field in and of itself. [40:16] Each of these things could be a [00:00] whole workshop lesson. This is [00:00] like a survey course.

[40:26] Node [00:00] doesn't have a formal [00:00] conventions for cli-configs, the [00:00] best in class library, free [00:00] standards consumption.

[40:33] Basically, I want to say, "Hey, [00:00] you, library, I want to really [00:00] import you. I'm just going to [00:00] give you the name of my thing. [00:00] You go through all the [00:00] standardized junk of like, look [00:00] here, look here, look here, look [00:00] here, resolve, resolve, resolve, [00:00] and then just give me an object [00:00] with all the configs. That's all [00:00] I want.

[40:47] I don't care where [00:00] you go. It's going to go to [00:00] package.json, JSON YAML file, RC [00:00] file, YAML, yaml-js, or .config. [00:00] js file.

[40:55] I don't care. I just [00:00] do the standard thing that [00:00] everyone expects. Everyone has [00:00] their own preferences. These are [00:00] the accepted defaults in the [00:00] Node community. Fine. I'm going [00:00] to use that.

[41:06] I'm personally [00:00] not a fan of this much [00:00] variability in your [00:00] configuration because it's very [00:00] annoying for beginners to go [00:00] like, "Have you checked this? [00:00] Have you checked this ? Have you [00:00] checked this?" A community has [00:00] very strong standards. You get a [00:00] lot by just leveraging off of [00:00] that.

[41:25] Let's install [00:00] cosmiconfig.

[00:00] We're going to [00:00] use it again in our dev command. [41:48] I'm just going to chuck it up in [00:00] here, the require, that is. Then [00:00] you have to tell cosmiconfig [00:00] what your field is.

[00:00] That's [00:00] essentially the only thing that [00:00] it has to know. I'm going to [00:00] call it CLI Workshop my name. [00:00] Feel free to call it whatever. [00:00] It really doesn't matter.

[00:00] It's going to search up [00:00] according to its internal [00:00] algorithm. It's going to load... [42:17] What the hell is the path to [00:00] config? I screwed up this one. [00:00] Hang on. I see the docs. I might [00:00] need to take a PR for this, [00:00] whatever this path to config is.

[42:31] I think it's just search for. [42:49] Yeah, this is an error in the [00:00] code itself. We'll just log it [00:00] out. We'll see what happens. No [00:00] fears. Debug config found for [00:00] something like that, whatever.

[43:15] Let's put in some fake config. [00:00] Let's put it here. Sorry, I'm [00:00] jumping around a lot. I know [00:00] it's hard to follow sometimes. [43:30] Let's do a.myapprc.json file.

[00:00] This is the CLI repo and I'm [00:00] putting the config for the CLI [00:00] repo in the CLI repo. That's bad, [00:00] but just work with me.

[00:00] I'm [00:00] taking that string that I want, [00:00] and add rc.json file. This is an [00:00] object that just says like foo. [00:00] Bar. I hope to retrieve this [00:00] configuration using cosmiconfig [00:00] packet. Let's just call this [00:00] name foo.Bar.

[44:09] I hope to [00:00] retrieve that information using [44:16] cosmiconfig. I don't really know [00:00] what search for returns. There's [00:00] no TypeScript help. We'll just [00:00] try and wing it and see what [00:00] happens. debug=CLIworkshop*.

[00:00] Config found for object Object. [44:48] I have to stringify it. [00:00] Goddamnit.

[45:06] Look at that. It [00:00] found the config. Config found [00:00] for config name foo.Bar file [00:00] path. Fantastic. That means I [00:00] don't need to load it because I [00:00] already did load it, you doofus. [00:00] That means I can just do this, [00:00] and I think that'd be fine.

[45:28] I [00:00] could care less about the file [00:00] path. That might be useful [00:00] reporting config file for blah, [00:00] blah, blah from here. That's [00:00] useful for debugging because [00:00] like, now you know where the [00:00] config is filing. In case you [00:00] have two different configs [00:00] conflicting, you know how things [00:00] are resolved. That's great.

[45:52] I'm not a fan of the way that [00:00] there's so many different ways [00:00] to do configs, but whatever.

[45:56] Now we can merge it. I have this [00:00] config object and I can just say [00:00] flags.name. Flags should [00:00] supersede config. You like that? [00:00] I don't think this works [00:00] actually. I don't know.

[46:24] This [00:00] is the new false nullish [00:00] coalescing operator from [00:00] TypeScript. I'm using TypeScript [00:00] 3.7, which has it. TypeScript 3. [00:00] 3. What junk is this? Get it out [00:00] of my sight. Holy crap. Yarn add [00:00] the TypeScript at latest. Get [00:00] out of here with 3.3. Who uses 3. [00:00] 3? 3.7. Drink me. Always use 3.7.

[00:00] VS Code isn't happy. VS Code's [00:00] still on 363. Jesus.

[47:06] I'm just [00:00] going to hope that it runs. I [00:00] want to see what happens. What's [00:00] going to happen here is we're [00:00] going to run this. We're [00:00] hopefully going to get a name of... [00:00] Hopefully, this is accepted.

[00:00] You got me on foo.Bar. You see? [00:00] This foo.Bar is coming from the [00:00] config that we laid out over [00:00] here.

[47:33] This RC thing, which is [00:00] I'm not a fan of the RC. I could [00:00] see myself doing this. Put it in [00:00] the package.json like some [00:00] random field over here.

[47:46] Again, [00:00] some CI tools just don't accept [00:00] this. They validate your package. [00:00] json and that's super freaking [00:00] annoying. Never do that and [00:00] never stop being awesome, [00:00] whatever.

[48:03] I stuck it. Again, [00:00] I deleted the RC file. I stuck [00:00] it in my package.json. It's [00:00] still going to give me that [00:00] string that I stuck in there. [48:13] That's reading that config from [00:00] the CLI. Then the final test is [00:00] if I write this name, does it [00:00] overwrite? It should be [00:00] overwriting that name for me.

[48:25] Just because in my imagined [00:00] order of priorities, flags are [00:00] more malleable than configs, so [00:00] I let the configs overwrite the [00:00] flags. Whatever it is, you [00:00] should have some way of [00:00] resolving any potential [00:00] duplication. I don't think there [00:00] is a standard. If there is, [00:00] please let me know because I [00:00] have looked, but I don't think [00:00] there's any standard.

[48:48] It just [00:00] makes sense. If my name, [00:00] environment variable...First of [00:00] all, anyone who does this is [00:00] insane and should be fired, but [00:00] there should be some resolution [00:00] algorithm that is deterministic. [00:00] Cool.

[49:11] There's a question here [00:00] of Zack Jones, "Can you show how [00:00] you're using the config variable [00:00] again?"

[49:16] Sure. Remember I [00:00] wasn't sure what this returns, [00:00] so I console logged it and then [00:00] it showed me that it wasn't [00:00] object that containing both a [00:00] config key and a filepath key. I [00:00] logged out of filepath key and a [00:00] debug. I took this config and I [00:00] said flags.name nullish [00:00] coalescing config.name.

[49:43] I [00:00] hope everyone's familiar with [00:00] nullish coalescing. It's the [00:00] same thing as if undefines and [00:00] then...or flags.name=null then [00:00] return config.name, something [00:00] like that. That's what it [00:00] disrupts these two, or name=, [00:00] something like that. This is the [00:00] equivalent of that, but it just [00:00] takes away a lot of boilerplate.

[50:16] It helps disambiguate the [00:00] situation where flags.name=empty [00:00] string. This is falsy, but you [00:00] may want that intentionally. [50:29] Most of the time, you should be [00:00] using nullish coalescing. This [00:00] is by far the better default [00:00] especially, for example, flags. [00:00] name is zero, that is falsy as [00:00] well.

[50:41] A lot of the times when [00:00] we use the or-operator, we [00:00] actually mean to use the nullish [00:00] coalescing operator because this [00:00] is more permissive. This is [00:00] falsy, which is a very broad [00:00] concept. This is nullish which [00:00] equals to just null or undefined. [51:01] This is null undefined false, [00:00] whatever, so on and so forth.

[51:13] This is literally just null [00:00] undefined. That's the [00:00] equivalence there, no implicit [00:00] coercion. Fantastic.

[51:24] It's [00:00] particularly strong in React, [00:00] where you get this like check me [00:00] and then an and, or, an and then [00:00] foo component check the whatever.

[51:39] I don't think that's a very [00:00] strong point. Just ignore me [00:00] there. Everyone should use this [00:00] by default and then only when [00:00] you really mean falsy, then use [00:00] this. That's good enough. It [00:00] also means that we can use Elvis [00:00] operator and that's my favorite.

[51:58] We have figured out configs. I [00:00] think this is important. As you [00:00] can see, any CLI of a decent [00:00] size accumulates a bunch of [00:00] config. In fact, your challenge [00:00] will be to restrict the number [00:00] of different configurations, [00:00] because every single one is a [00:00] combinatorial explosion of, "Did [00:00] you consider this and this?" and [00:00] then how do you maintain that? [52:22] Doesn't make sense to new users.

[00:00] It's a Pandora's box, but it's [00:00] helpful, because it solves the [00:00] problem of, "Do you want to [00:00] enter this flag every time? Do [00:00] you want to have it prompt every [00:00] time?" No. The answer is no.

[52:40] That's config. We have two more [00:00] in our grand tour of CLI [00:00] thingies.

[52:50] One of the unfair [00:00] advantages of CLIs is being able [00:00] scaffold working code. This is [00:00] the heaviest part, but I think [00:00] it's super useful. Totally up to [00:00] you, whether you want to use [00:00] this or not. I think scaffolding [00:00] is super under-explored.

[53:10] Don't think about this as just [00:00] scaffolding. It's doing anything [00:00] known on-command. Anything heavy.

[53:20] It's a big part of Rails [00:00] productivity, Angular has built [00:00] the same with ng generate. In [00:00] fact, that's what create-react- [00:00] app does, with scaffolding out [00:00] your React App. It's proven to [00:00] work, and it's a whole bunch of [00:00] boilerplate you don't have to [00:00] maintain. You could take it over, [00:00] because it's code that you're [00:00] familiar with.

[53:36] Yeoman is one [00:00] of the main successful projects [00:00] in this arena. They are [00:00] specifically for scaffolding, [00:00] but it relies on global installs, [00:00] which you can't always assume.

[53:50] I'd much rather have it be like [00:00] my CLI scaffold, blah, alongside [00:00] of dev, build, test, eject [00:00] whatever. Instead of having a [00:00] drop I'd say, "Yo CLI workshop, [00:00] generate something."

[54:17] I don't [00:00] know. For me, it's a visual [00:00] preference. I want to keep [00:00] everything under the same [00:00] namespace. You just have a lot [00:00] more control over like, "Can you [00:00] code share? Can you reuse the [00:00] same authentication preferences?"

[54:30] Let's say I already have this [00:00] logged in, and that CLI has a [00:00] sense of what my user is, can I [00:00] reuse that same command in any [00:00] of my other codes? Can I use [00:00] that same information in any of [00:00] my other commands? The answer is [00:00] yes, frequently.

[54:50] Yeoman is [00:00] good for dumb scaffolding. I [00:00] prefer others.

[54:57] Express also [00:00] has a lot of templating. [00:00] Templating and scaffolding are [00:00] different beasts. Templating is [00:00] more for like you have a raw [00:00] template. It's a form of [00:00] accepted language, where you [00:00] fill in some data and then it [00:00] outputs HTML. Most of the time, [00:00] it's HTML. Sometimes it's code, [00:00] whatever.

[55:14] If you're server [00:00] side rendering HTML without any [00:00] frameworks, you're going to use [00:00] one of these, like Consolidate, [00:00] Mustache, EJS, Handlebars, [00:00] Liquid, so on and so forth.

[55:24] You've definitely seen Mustache [00:00] before. You may not have known [00:00] what it was. Let's have a look [00:00] at the syntax of mustache.

[55:32] It [00:00] literally is hello, double [00:00] braces thing, and then you have [00:00] one blah, blah, blah. This is [00:00] the template. You pass in an [00:00] object with all that data, and [00:00] it's going to just mush that [00:00] together to produce the final [00:00] output.

[55:47] That makes total [00:00] sense. You can apply it to [00:00] anything like HTML, JS, whatever. [00:00] It's just strings.

[55:54] A lot of [00:00] times, though, you want logic. [00:00] There's EJS for. EJS has some [00:00] looping, has some if then else, [00:00] whereas Mustache is just dumb [00:00] pasting. Here, you have an if [00:00] statement, and then if this is a [00:00] thing, then you put in that [00:00] block of text. I think there's [00:00] looping.

[56:19] Where's the looping? [00:00] For each. There you go inside of [00:00] the UO. This makes sense.

[56:25] You [00:00] could build a whole server side [00:00] rendering framework just from [00:00] these templating things, and [00:00] it's totally fine. Virtually, [00:00] these are all like one for one [00:00] things like on one request, [00:00] render this template, that's it. [56:37] Whereas, if you're scaffolding, [00:00] you typically want to dump out a [00:00] whole folder, and then from that [00:00] folder, just customize stuff.

[56:46] The best that I found, and this [00:00] is not what widely-known or big [00:00] in any way, is the one that just [00:00] does the thing that I want it to [00:00] do is copy template directory. [56:59] You want to dump entire folders [00:00] with code.

[00:00] The best way for [00:00] me to demonstrate this is to [00:00] show you nullify dev and the [00:00] thing that I worked on. It's not [00:00] a sales pitch. I just want to [00:00] show you this is the thing that [00:00] we do to help people get up to [00:00] speed on serverless functions.

[57:19] We have source JS template [00:00] function templates. JS. These [00:00] are all the templates that we [00:00] have to say that if you will [00:00] scaffold a nullify function, [00:00] we'll give you an autocomplete [00:00] of all this. Let's say you want [00:00] to graphic template. I just want [00:00] a graphical function, type it in [00:00] there, and you get this and this [00:00] and this. Then you just pick [00:00] this one.

[57:46] You get this nice [00:00] little function. It dumps it [00:00] right into your code base. It's [00:00] guaranteed in the right spot [00:00] with the right working code. You [00:00] can just npm start and run it. [57:57] That's the rough idea.

[00:00] Imagine, for anyone building, a [00:00] library of existing use cases or [00:00] a platform serving developers. [00:00] You could serve them a whole [00:00] bunch of documentation, or you [00:00] could just ship it in a CLI. [00:00] Inside of CLI, says, "Here, pick [00:00] the thing that you want, punch [00:00] it in," most people are going to [00:00] expect that every single use [00:00] that every use case is accounted [00:00] for.

[58:24] I want authentication. [00:00] Done. You have working code [00:00] there, and they can go ahead and [00:00] modify it. That's the rough idea.

[00:00] What we're going to do, this is [00:00] very tricky. The other thing you [00:00] should also note is what your in [00:00] directory and your sourcing and [00:00] destination directory is. You [00:00] have a source coming from within [00:00] your CLI to the project repo [00:00] that your users applying it on, [00:00] if that makes sense.

[58:57] Here, [00:00] the sample code indexes from [00:00] process.cwd, the current working [00:00] directory, this is wrong. You [00:00] should be taking from inside of [00:00] your CLI directory to your [00:00] source directory.

[59:14] I'm doing a [00:00] lot of hand actions. I should [00:00] probably show an example.

[59:22] Here's where we try to make your [00:00] CLI scaffold out from a source [00:00] folder contained within it to a [00:00] folder that the user inputs. [59:33] We're going to create new [00:00] commands. Let's just get rid of [00:00] this build command and we just [00:00] call it generate command.

[59:44] My [00:00] VS Code is not responding right [00:00] now. I rename this. I'm going to [00:00] call this a generate command [00:00] instead of a build command. My [00:00] VS Code is still on type 3836. [00:00] That's why it's giving an error, [00:00] but it's actually fine in [00:00] production.

[60:13] I regenerate [00:00] command. I'm going to ask for a [00:00] name. I'm going to get rid of [00:00] all of this stuff, just make it [00:00] way simple. I just want to focus [00:00] on the thing at hand.

[60:38] This is [00:00] one of the slightly harder [00:00] things to do the first time you [00:00] do it. You do need a little bit [00:00] of concentration as to what [00:00] you're doing. Name your folder, [00:00] whatever.

[61:09] It's going to [00:00] prompt me for a name, and then [00:00] I'm going to copy out a folder. [00:00] Inside of the references, I have [00:00] prepared an example React app [00:00] that I'm just going to copy over [00:00] into my node modules into...I'm [00:00] going to have a template folder. [00:00] Inside of templates folder, I'm [00:00] going to have a React module. [00:00] It's going to try and copy out a [00:00] source in a disc.

[61:48] Let's [00:00] install copy template there. [00:00] We're going to take a break soon [00:00] because it's been another hour. [00:00] I just wanted to show you this. [00:00] You can take the next 15 minutes [00:00] to implement any scaffolding [00:00] code of your own.

[62:06] You don't [00:00] have to use my code. Use [00:00] whatever you're working with [00:00] currently. Just dump that into a [00:00] Templates folder and then copy [00:00] it up.

[62:15] I have copy-template- [00:00] dir, and then I'm going to copy [00:00] a whole bunch of this stuff. [00:00] There we go. Hang on. Instead of [00:00] inDir outDir, I'm going to say [00:00] sourceDir destDir. I don't like [00:00] the way that it's...

[63:05] The [00:00] source comes from inside of the [00:00] CLI. I should actually use the [00:00] dirname module or...There's this, [00:00] and then there's also path. [00:00] resolve.

[63:21] I'm always [00:00] uncomfortable around known [00:00] resolution. I generally tend not [00:00] to use path.resolve. I generally [00:00] tend to use path.join of the [00:00] dirname which is appointed [00:00] directly to this generate file.

[00:00] I have to go up and -- I'll just [00:00] stick it in there -- up and [00:00] across. I don't have to do that. [00:00] I just pick Rollup React by name, [00:00] rollup react. I hope that's [00:00] right. If it's wrong, you'll see [00:00] me debug. I've done this a fair [00:00] amount of times.

[64:12] We can also [00:00] then use a promise-based [00:00] alternative because promises are [00:00] great. The author of copy- [00:00] template-dir refuses to use [00:00] promises for God knows what [00:00] reason.

[64:33] We have something [00:00] basically running. I think I [00:00] have some errors here. Screw the [00:00] errors. Let's try it out.

[64:45] One [00:00] thing to note, I should not have [00:00] the node modules installed here [00:00] because that will be a lot of [00:00] files. I'm going to copy over [00:00] the files and then run yarn [00:00] install or npm install.

[65:01] I'm [00:00] in my repo. I'm going to go up [00:00] one level, and then make-dir [00:00] newProject, and then I'm inside [00:00] of newProject.

[65:20] I'm in a [00:00] different project. Now I run the [00:00] generate command. Let's see if [00:00] this works. It'll probably have [00:00] some bugs. There we go. "Cannot [00:00] destructure property config [00:00] undefined or null."

[65:45] I don't [00:00] know what that is, but I'm going [00:00] to choose to ignore it right now, [00:00] and then foo.Bar. Let's go. " [00:00] Cannot find module utils." What [00:00] is the node? node util.promisify. [00:00] util, singular.

[66:10] Thanks, Chris. [00:00] Thanks.

[00:00] A bug. " [00:00] AssertionError -- object is not [00:00] a function." I may not have [00:00] promisified it correctly. What [00:00] is vars? Hang on. I think I [00:00] screwed up with, what is vars.

[66:41] Vars is the replacement [00:00] variables, which I don't need to... [00:00] I didn't specify it. I don't use [00:00] this, but let's keep it in there [00:00] anyway.

[00:00] Object = function? [00:00] Callback needs to be a function. [00:00] Why am I passing in an object? I [00:00] should be promisifying it. util. [00:00] promisify promisifies this way. [67:22] I don't see an issue with my [00:00] promisifying. I don't see it.

[67:33] In the interest of time, I'm [00:00] going to do the non-promisified [00:00] way, because right now, it's the [00:00] same thing. I have slightly less [00:00] respect for myself, and that's [00:00] totally fine.

[67:55] Me and my [00:00] TypeScript. I'm not really using [00:00] it to the fullest of my ability. [68:03] I've got to dig into that error [00:00] there. There is some oclif/ [00:00] config error somewhere in there, [00:00] which I've never seen, but [00:00] whatever.

[68:14] Bug. Look at that! [00:00] Inside of newProject, it's [00:00] scaffolded out a working React [00:00] App.

[68:23] Now I can cd into bug, [00:00] and then yarn install. I've [00:00] installed this previously, so [00:00] it's going to rehydrate from [00:00] cache. It's linking a whole [00:00] bunch of things.

[00:00] You see how [00:00] I created a generate command. [00:00] That's the basics of a create- [00:00] react-app. Obviously, a create- [00:00] react-app also has React scripts, [00:00] which is the core library that [00:00] you're maintaining. You could [00:00] easily extract that from all of [00:00] this stuff.

[69:04] Assuming this [00:00] doesn't choke, I can run yarn [00:00] start, which runs Rollup, and [00:00] runs TypeScript as well. It's [00:00] going to start a new React App [00:00] for me. If you're following [00:00] along, take any sample repo that [00:00] you have, and chuck it into the [00:00] templates folder. This is where [00:00] you shine, in terms of what you [00:00] want to do with your scaffolding.

[69:38] I have a React App. I scaffolded [00:00] this with my custom-created [00:00] generate command, down in here. [69:52] This is where I encourage you to [00:00] explore what we did here.

[00:00] I [00:00] know I went a little bit fast, [00:00] but I think it's still [00:00] followable, in terms of...The [00:00] code that we did, wasn't that [00:00] much. It was, copy from a source, [00:00] and then copy to a destination.

[70:10] You have this, and then you can [00:00] easily imagine, like, "OK, I [00:00] have one template. What if I [00:00] have a hundred different [00:00] templates?"

[70:19] I can go through [00:00] my templates folder, generate an [00:00] autocomplete list, and then type [00:00] in, "I just want to scaffold [00:00] these things out, and scaffold [00:00] it up."

[70:25] This is tooling their [00:00] Yeoman, so you're perfectly in [00:00] command of changing the [00:00] developer experience to make it [00:00] what you want.

[70:36] We're at the [00:00] two-hour mark, so I'm going to [00:00] take a break for 15. We'll come [00:00] back in 15 minutes, at the half- [00:00] hour, and continue from there.

[70:47] I'll stay around for some [00:00] questions, but otherwise, thanks [00:00] for following along for the [00:00] first half.

egghead
egghead
~ an hour ago

Member comments are a way for members to communicate, interact, and ask questions about a lesson.

The instructor or someone from the community might respond to your question Here are a few basic guidelines to commenting on egghead.io

Be on-Topic

Comments are for discussing a lesson. If you're having a general issue with the website functionality, please contact us at support@egghead.io.

Avoid meta-discussion

  • This was great!
  • This was horrible!
  • I didn't like this because it didn't match my skill level.
  • +1 It will likely be deleted as spam.

Code Problems?

Should be accompanied by code! Codesandbox or Stackblitz provide a way to share code and discuss it in context

Details and Context

Vague question? Vague answer. Any details and context you can provide will lure more interesting answers!

Markdown supported.
Become a member to join the discussionEnroll Today