1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
//! # Using the scaffolding CLI tool:
//!
//! After having installed it, these are the commands that are available:
//!
//! ```bash
//! // Scaffold an example Holochain app
//! hc-scaffold example
//! ```
//!
//! Or if you want to scaffold your own custom app, for example a to-do app:
//!
//! ```bash
//! // Scaffold an empty web-app, using a built-in template
//! hc-scaffold web-app todos
//!
//! cd todos
//!
//! // Scaffold a dna inside the newly scaffolded app
//! hc-scaffold dna todos
//!
//! // Scaffold a zome inside the newly scaffolded dna
//! hc-scaffold zome todos
//!
//! // Scaffold an entry-type inside the newly scaffolded zome
//! hc-scaffold entry-type todo
//!
//! // Scaffold a collection for the newly scaffolded entry-type
//! hc-scaffold collection global all_todos
//!
//! // Scaffold a new link-type
//! hc-scaffold link-type
//!
//! // Will show all the commands that are available
//! hc-scaffold --help
//! ```
//!
//! # Custom Templates
//!
//! The scaffolding tool comes with 6 built-in templates:
//!
//! - Vue (with TypeScript)
//! - Svelte (with TypeScript)
//! - Lit (with TypeScript)
//! - React (with TypeScript)
//! - Vanilla
//! - Headless (no ui) - [best for building headless hApps or using a unsupported ui framework without having to create a custom template]
//!
//! These templates provide most of the skeleton you need to start your own holochain app.
//!
//! But! They are not complete, nor provide good design from the UI/UX perspective. They are trying to be unopinionated in that regard, so that you as the developer can apply your own style of building frontend apps.
//!
//! To allow for more flexibility, the scaffolding tool can be extended and customized using custom templates. This would allow you to create a "React" template, or a "Vue + tailwind" template, or whatever style of frontend code and packaging you want for your app.
//!
//! ## Using custom templates
//!
//! All `hc scaffold` commands accept an optional `--template` argument. This argument can be:
//! - Either one of the built-in templates:
//! - "vue"
//! - "svelte"
//! - "lit"
//! - "react"
//! - "vanilla"
//! - "headless" (no ui)
//! - Or a path to a custom template.
//! - E.g `hc-scaffold --template ./path/to/custom/template/folder web-app`
//!
//! If you know of some already existing custom template, look first in the documentation of that template for instructions on how to use it, in case the template offers a nix wrapper command, which is much easier to use.
//!
//! Otherwise, you can just clone it and use it like this:
//!
//! `hc scaffold --template ./path/to/custom/template web-app forum`
//!
//! ## Template config
//!
//! When you run `hc-scaffold --template ./path/to/custom/template/folder web-app` or `hc-scaffold example` for the first time, the root `package.json` file
//! of the scaffolded project will contain an `"hcScaffold"` field with some configuration about the template used to scaffold
//! the project:
//!
//! ```json
//! {
//! "hcScaffold": {
//! "template": "/* template name i.e. react/vue/svelte/lit/vanilla or path to custom template */"
//! }
//! }
//! ```
//!
//! The scaffolding CLI will read from this config for subsequent commands, so you do not need to explicitly
//! pass the `--template` flag for every command. This also provides a guardrail to prevent mixing up templates
//! for different `hc-scaffold` commands.
//!
//! ## How to create a custom template
//!
//! Creating and maintaining your own template can be challenging at first, so look for existing templates that you can reuse before diving in to create your own.
//!
//! The best way to start creating a custom template is to go from one of the built-in ones, and modify it.
//!
//! To create a custom template, execute these steps:
//!
//! 1. Run this command:
//! - Through holonix:
//!
//! ```bash
//! nix run github:holochain/holonix#hc-scaffold -- template new
//! ```
//! - Outside holonix if the cli was installed via `cargo install holochain_scaffolding_cli`
//!
//! ```bash
//! hc-scaffold template new
//! ```
//! 2. A new dir will be created in the current directory. Check this new folder in a version control system like git.
//! 3. Replace all instances of `<TODO:REPLACE_ME_WITH_CUSTOM_TEMPLATE_GIT_URL>` in its `README.md` file with the appropriate git URL (eg. "github:holochain-open-dev/templates").
//! 4. Replace all instances of `<TODO:REPLACE_ME_WITH_CUSTOM_TEMPLATE_GIT_URL>` in its `template/web-app/flake.nix.hbs` file with the appropriate git URL (eg. "github:holochain-open-dev/templates").
//!
//! That's it! At this point you will have a correctly functioning custom template repository with tests, a `README.md` documenting how to use it, and a `template` folder. That's where your custom template lives.
//!
//! Templates have this directory structure:
//!
//! coordinator-zome/
//! dna/
//! entry-type/
//! example/
//! field-types/
//! collection/
//! integrity-zome/
//! link-type/
//! web-app/
//!
//! Each folder corresponds to the templates that are created when running a specific command. Here are the steps executed:
//!
//! 1. The user executes a scaffolding command, like `hc scaffold web-app`.
//! - Optionally, they may pass a `--template` argument, specifying the template name or local path to use.
//! - The `--template` value is saved in the root `package.json` file's `hcScaffold` key for future reference and to prevent the use of different templates in the same project.
//! 2. The scaffolding tool prompts the user to input all necessary information for the command.
//! 3. **Backend** code is automatically generated, independent of custom templates.
//! 4. If a `--template` argument is provided:
//! - A check is performed to ensure alignment with the originally scaffolded hApp. If successful, it uses the provided template.
//! 5. For the selected template, the tool searches for a corresponding folder inside it based on the command.
//! - For example, `hc scaffold web-app` searches for a folder named `web-app`.
//! 6. If found, it copies the directory structure within that folder, selecting files with the `.hbs` extension.
//! 7. It renders the contents of each file using appropriate data from the command.
//! - For instance, in `hc scaffold web-app`, one context field is `app_name`, representing the user-input app name.
//! 8. Finally, it merges the resulting directory structure with the existing repository. If a file already exists, its contents are overwritten.
//!
//! You can take a look at [Writing templates](#writing-templates) to learn how to write your own templates.
//!
//! This is the list of commands and the templates they use:
//!
//! - `web-app`: uses the `web-app` folder. [Available data](`crate::templates::web_app::ScaffoldWebAppData`).
//! - `dna`: uses the `dna` folder. [Available data](`crate::templates::dna::ScaffoldDnaData`).
//! - `zome`: uses the `coordinator-zome` folder if scaffolding a coordinator zome, and the `integrity-zome` folder if scaffolding an integrity zome. [Available data](`crate::templates::coordinator::ScaffoldCoordinatorZomeData`).
//! - `entry-type`: uses the `entry-type` folder. [Available data](`crate::templates::entry_type::ScaffoldEntryTypeData`).
//! - `link-type`: uses the `link-type` folder. [Available data](`crate::templates::link_type::ScaffoldLinkTypeData`).
//! - `collection`: uses the `collection` folder. [Available data](`crate::templates::collection::ScaffoldCollectionData`).
//! - `example`: uses the `example` folder. [Available data](`crate::templates::example::ScaffoldExampleData`).
//!
//! ### Field types
//!
//! The `field-types` folder is special. It has the following directory structure:
//!
//! ActionHash/
//! type.hbs
//! AgentPubKey/
//! type.hbs
//! bool/
//! Checkbox/
//! detail/
//! render.hbs
//! edit/
//! imports.hbs
//! render.hbs
//! type.hbs
//! EntryHash/
//! type.hbs
//! String/
//! TextField/
//! detail/
//! render.hbs
//! edit/
//! imports.hbs
//! render.hbs
//! TextArea/
//! detail/
//! render.hbs
//! edit/
//! imports.hbs
//! render.hbs
//! type.hbs
//! Timestamp/
//! DateTimePicker/
//! detail/
//! render.hbs
//! edit/
//! imports.hbs
//! render.hbs
//! type.hbs
//! u32/
//! Slider/
//! detail/
//! render.hbs
//! edit/
//! imports.hbs
//! render.hbs
//! type.hbs
//!
//! As you can see, the top-level folders are the rust types that are possible to use as the field types for an entry. The `type.hbs` file in each of the folders contains the typescript type for that rust type, so that it can be rendered in the frontend.
//!
//! Now, on to the interesting part. Each subfolder in each of the types **corresponds to a frontend widget** that that field can be rendered with. The scaffolding tool will dynamically pick up the folders that exist in that type, and offer the choice to the user to pick a widget from the supported ones in this template. If no widget is found, then the user won't be able to make the field visible in the frontend.
//!
//! After the user has selected a widget, it's up to the templates inside the `entry-type` folder in the root of the template to render the widget, using partials. All the `field-types` directory is registered as partials in the handlebars engine.
//!
//! So for example, if inside the `entry-types` template we have something like this:
//!
//! ```hbs
//! {{> String/TextArea/detail/render }}
//! ```
//!
//! This will get replaced by the contents of the file `field-types/String/TextArea/detail/render.hbs`.
//!
//! ### Instructions
//!
//! Additionally to the folders, you can override the built-in instructions that get shown to the user after each command. The scaffolding tool will look for a file named `<COMMAND>.instructions.hbs` in the folder for the custom template, and if it exists, render its contents and display them to the user. The name of the `COMMAND` for the file matches the names for the folders where the templates for each command exist.
//!
//! This is a great way to guide the users of your template towards next steps that they need to take while using your template.
//!
//! So for example, if there is a `coordinator-zome.instructions.hbs` file in the root folder of your template and the user runs `hc scaffold zome posts --coordinator dnas/forum/zomes/coordinator`, then the scaffolding tool will render its contents and display them to the user when it has finished creating the zome.
//!
//! ### Writing templates
//!
//! The template engine used in the template files is [handlebars](https://handlebarsjs.com/). You can look at its documentation to learn how to write your own templates.
//!
//! Here are the available helpers:
//! - All the built-in helpers described in [the handlebars crate](https://docs.rs/handlebars/latest/handlebars/#built-in-helpers).
//! - Case helpers:
//! - `pascal_case`: converts the string to pascal case.
//! - `title_case`: converts the string to title case.
//! - `lower_case`: converts the string to lower case.
//! - `snake_case`: converts the string to snake case.
//! - `camel_case`: converts the string to camel case.
//! - `plural`: converts the given string to its plural.
//! - `concat`: concatenize strings.
//! - `contains`: check whether list contains an element.
//! - Example usage:
//! ```hbs
//! {{#if (contains entry_type_list "Profile")}}
//! ...
//! {{/if}}
//! ```
//! - `includes`: check whether a string includes a substring.
//! - Example usage:
//! ```hbs
//! {{#if (includes entry_type.name "Profile")}}
//! ...
//! {{/if}}
//! ```
//! - `merge` and `match_scope`: a pair of helpers useful to add some new code to an already existing code structure, respecting their scope (`{` and `}`) structure.
//! - `merge`: takes existing code as its only argument.
//! - `match_scope`: needs to be placed inside a `merge` helper block, and takes the opening of an scope as only argument. It then searches the argument of the `merge` helper for a scope matching that opening of the scope, and replaces its contents with the contents of the `match_scope` block:
//! - Example usage:
//! ```hbs
//! {{#merge previous_file_content}}
//! {{#match_scope "export class ExistingClassA {" }}
//! {{previous_scope_content}} // Variable containing the previous content of the scope
//!
//! newFunction() {
//! // This is a new function that will be added at the end of "ExistingClassA"
//! }
//! {{/match_scope}}
//! {{#match_scope "export class ExistingClassB {" }}
//!
//! {{#merge previous_scope_content}}
//! {{#match_scope "newFunction() {" }}
//! {{previous_scope_content}}
//! // Will add a line at the end of newFunction
//! {{/match_scope}}
//! {{/merge}}
//!
//! {{/match_scope}}
//! {{/merge}}
//! ```