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 5 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.