@genesislcap/build-kit 14.142.1-alpha-2787e5d.0 → 14.143.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/utils/archive.d.ts +2 -0
  2. package/dist/utils/archive.d.ts.map +1 -0
  3. package/dist/utils/archive.js +20 -0
  4. package/dist/utils/index.d.ts +5 -1
  5. package/dist/utils/index.d.ts.map +1 -1
  6. package/dist/utils/index.js +5 -1
  7. package/dist/utils/npm.d.ts +1 -1
  8. package/dist/utils/npm.d.ts.map +1 -1
  9. package/dist/utils/npm.js +4 -2
  10. package/dist/utils/prompts.d.ts.map +1 -1
  11. package/dist/utils/prompts.js +7 -5
  12. package/dist/utils/seed.fetching.d.ts +36 -0
  13. package/dist/utils/seed.fetching.d.ts.map +1 -0
  14. package/dist/utils/seed.fetching.js +157 -0
  15. package/dist/utils/seed.meta.d.ts +10 -0
  16. package/dist/utils/seed.meta.d.ts.map +1 -0
  17. package/dist/utils/seed.meta.js +38 -0
  18. package/dist/utils/seed.processing.d.ts +4 -0
  19. package/dist/utils/seed.processing.d.ts.map +1 -0
  20. package/dist/utils/seed.processing.js +87 -0
  21. package/dist/utils/seed.setup.d.ts +3 -0
  22. package/dist/utils/seed.setup.d.ts.map +1 -0
  23. package/dist/utils/seed.setup.js +73 -0
  24. package/package.json +6 -3
  25. package/src/utils/archive.ts +18 -0
  26. package/src/utils/index.ts +5 -1
  27. package/src/utils/npm.ts +4 -2
  28. package/src/utils/prompts.ts +8 -5
  29. package/src/utils/seed.fetching.ts +256 -0
  30. package/src/utils/seed.meta.ts +34 -0
  31. package/src/utils/seed.processing.ts +100 -0
  32. package/src/utils/seed.setup.ts +98 -0
  33. package/test/fixtures/mock-seed/.genx/.genxrc.js +7 -0
  34. package/test/fixtures/mock-seed/.genx/configure.js +3 -0
  35. package/test/fixtures/mock-seed/.genx/package.json +17 -0
  36. package/test/fixtures/mock-seed/test.md +44 -0
  37. package/test/utils/seed.fetching.test.ts +145 -0
  38. package/test/utils/seed.meta.test.ts +33 -0
  39. package/dist/utils/seed.d.ts +0 -31
  40. package/dist/utils/seed.d.ts.map +0 -1
  41. package/dist/utils/seed.js +0 -264
  42. package/src/utils/seed.ts +0 -351
package/src/utils/npm.ts CHANGED
@@ -64,7 +64,7 @@ Response if multiple versions available:
64
64
  @genesislcap/genx@14.41.0 '14.41.0'
65
65
  @genesislcap/genx@14.41.1 '14.41.1'
66
66
  */
67
- export const getLatestVersion = (name, range = 'latest') => {
67
+ export const getLatestVersion = (name, range = 'latest', verbose = true) => {
68
68
  let result;
69
69
 
70
70
  try {
@@ -79,7 +79,9 @@ export const getLatestVersion = (name, range = 'latest') => {
79
79
  .pop()
80
80
  .replace(/'/g, '');
81
81
  } catch (e) {
82
- consola.error(`Could not fetch ${name} version ${range} information from the registry`);
82
+ if (verbose) {
83
+ consola.error(`Could not fetch ${name} version ${range} information from the registry`);
84
+ }
83
85
  }
84
86
 
85
87
  return result;
@@ -94,6 +94,7 @@ const getCLIParams = (
94
94
  validate,
95
95
  ) => {
96
96
  const base = skipOptionalPrompts ? defaults : {};
97
+
97
98
  const cliParams = {
98
99
  ...base,
99
100
  ...persistedAnswers,
@@ -104,11 +105,13 @@ const getCLIParams = (
104
105
  appSeed: {
105
106
  type: seedMeta.local ? 'local' : 'remote',
106
107
  name: seedMeta.seed,
107
- about: details.description || '',
108
- remoteUser: seedMeta.user,
109
- remoteRepo: seedMeta.repo,
110
- remoteBranch: seedMeta.ref,
111
- subdir: seedMeta.subdir || '',
108
+ description: details.description,
109
+ version: details.version,
110
+ repo: seedMeta.repo,
111
+ artifact: seedMeta.artifact,
112
+ ref: seedMeta.ref,
113
+ subdir: seedMeta.subdir,
114
+ date: new Date(),
112
115
  },
113
116
  };
114
117
  const validationErrors = validate(cliParams);
@@ -0,0 +1,256 @@
1
+ import { cpSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { relative, sep } from 'node:path';
3
+ import { consola } from 'consola';
4
+ import getNpmTarballUrl from 'get-npm-tarball-url';
5
+ import { downloadTemplate } from 'giget';
6
+ import type { PackageJson } from 'pkg-types';
7
+ import { downloadTarball } from './archive';
8
+ import { bold } from './console';
9
+ import { rm } from './fs';
10
+ import { getLatestVersion } from './npm';
11
+ import { loadGenxModule } from './seed.meta';
12
+ import { removeExclusions } from './seed.processing';
13
+
14
+ export type LocalFetchResult = {
15
+ local: boolean;
16
+ genxDir: string;
17
+ seed: string;
18
+ subdir: string; // seed subdir within seed repo (if set)
19
+ pkg: PackageJson;
20
+ dir: string; // app directory e.g. myapp
21
+ extractedSeedDir: string; // directory to extract seed: for init operation same as dir, for add operaion dir/.genx/tmp
22
+ extractedSeedGenxDir: string;
23
+ };
24
+
25
+ export type RemoteGitFetchResult = {
26
+ repo: string;
27
+ ref: string;
28
+ id: string;
29
+ };
30
+
31
+ export type RemoteNPMFetchResult = {
32
+ artifact: string;
33
+ ref: string;
34
+ };
35
+
36
+ export type RemoteFetchResult = RemoteGitFetchResult | RemoteNPMFetchResult;
37
+
38
+ export type FetchResult = LocalFetchResult | (LocalFetchResult & RemoteFetchResult);
39
+
40
+ export const resolveGitSeed = (
41
+ seed = '',
42
+ ref = 'main',
43
+ subdir = '',
44
+ provider = 'gh',
45
+ ): RemoteGitFetchResult => {
46
+ const parts = seed.split('/');
47
+ const repo = parts.pop() || 'blank-app-seed';
48
+ const user = parts.pop() || 'genesiscommunitysuccess';
49
+ const dir = subdir ? `/${subdir}` : '';
50
+ return {
51
+ repo: `${user}/${repo}`,
52
+ ref,
53
+ // Giget-compatible template ID
54
+ id: `${provider}:${user}/${repo}${dir}#${ref}`,
55
+ };
56
+ };
57
+
58
+ export const resolveNPMSeed = (
59
+ seed = '',
60
+ ref = 'latest',
61
+ useDefaultNPMScope = true,
62
+ ): RemoteNPMFetchResult => {
63
+ const parts = seed.split('/');
64
+ const moduleName = parts.pop() || 'blank-app-seed';
65
+ const scope = parts.pop() || (useDefaultNPMScope ? 'genesislcap' : '');
66
+ const scopeFormatted = scope ? `@${scope}/` : '';
67
+ const artifact = `${scopeFormatted}${moduleName}`;
68
+ return {
69
+ artifact,
70
+ ref,
71
+ };
72
+ };
73
+
74
+ export type SeedDirs = {
75
+ isLocalSeed: boolean;
76
+ dir: string;
77
+ subdir: string;
78
+ localPath: string;
79
+ genxDir: string;
80
+ extractedSeedDir: string;
81
+ extractedSeedGenxDir: string;
82
+ };
83
+
84
+ export const getSeedDirs = (
85
+ seed: string,
86
+ dir: string,
87
+ subdir: string,
88
+ isInit: boolean,
89
+ remote: boolean,
90
+ ): SeedDirs => {
91
+ const GENX_DIR = '/.genx';
92
+ const TMP_DIR = '/tmp';
93
+ const localPath = subdir ? `${seed}/${subdir}` : seed;
94
+ const isLocalSeed = !remote && existsSync(localPath);
95
+ const genxDir = relative(process.cwd(), `${dir}${GENX_DIR}`);
96
+ const extractedSeedDir = isInit ? dir : relative(process.cwd(), `${dir}${GENX_DIR}${TMP_DIR}`);
97
+ const extractedSeedGenxDir = isInit
98
+ ? genxDir
99
+ : relative(process.cwd(), `${extractedSeedDir}${GENX_DIR}`);
100
+ return {
101
+ localPath,
102
+ dir,
103
+ subdir,
104
+ isLocalSeed,
105
+ genxDir,
106
+ extractedSeedDir,
107
+ extractedSeedGenxDir,
108
+ };
109
+ };
110
+
111
+ const copyLocalSeed = (dirs: SeedDirs, ref: string, start: number): LocalFetchResult => {
112
+ const { dir, subdir, localPath, extractedSeedDir, genxDir, extractedSeedGenxDir } = dirs;
113
+ consola.info(`Copying local seed ${bold(localPath)}`);
114
+ try {
115
+ if (ref) {
116
+ consola.warn(`${bold('ref')} has no effect when used with a local seed`);
117
+ }
118
+ cpSync(localPath, extractedSeedDir, { recursive: true });
119
+ consola.success(`Seed copied in ${Date.now() - start}ms`);
120
+ return {
121
+ local: true,
122
+ genxDir,
123
+ seed: localPath,
124
+ subdir,
125
+ pkg: loadGenxModule(extractedSeedGenxDir),
126
+ dir,
127
+ extractedSeedDir,
128
+ extractedSeedGenxDir,
129
+ };
130
+ } catch (e) {
131
+ consola.error(`Error copying ${localPath} to ${bold(dir)}`, e);
132
+ process.exit(1);
133
+ }
134
+ };
135
+
136
+ const fetchNPMSeed = async (
137
+ seed: string,
138
+ ref: string,
139
+ dirs: SeedDirs,
140
+ useDefaultNPMScope: boolean,
141
+ start: number,
142
+ ) => {
143
+ let success = true;
144
+ const data = resolveNPMSeed(seed, ref, useDefaultNPMScope);
145
+
146
+ consola.info(
147
+ `Downloading NPM module ${bold(data.artifact)} (dist tag ${bold(data.ref)}, subdir: ${bold(
148
+ dirs.subdir || 'N/A',
149
+ )}) to ${bold(dirs.dir)} directory...`,
150
+ );
151
+
152
+ try {
153
+ const version = getLatestVersion(data.artifact, data.ref, false);
154
+ const url = getNpmTarballUrl(data.artifact, version);
155
+ // NPM tarballs contains top-level directory called package
156
+ await downloadTarball(url, dirs.extractedSeedDir, `${sep}package`).catch((err) => {
157
+ consola.warn('Error downloading module', err);
158
+ success = false;
159
+ });
160
+ } catch (err) {
161
+ success = false;
162
+ consola.warn('Error fetching version information from the registry');
163
+ }
164
+
165
+ // If NPM download successful return result, otherwise fall back to Git download
166
+ if (success) {
167
+ consola.success(`Seed downloaded in ${Date.now() - start}ms`);
168
+
169
+ return {
170
+ local: false,
171
+ genxDir: dirs.genxDir,
172
+ seed,
173
+ subdir: dirs.subdir,
174
+ pkg: loadGenxModule(dirs.extractedSeedGenxDir),
175
+ dir: dirs.dir,
176
+ extractedSeedDir: dirs.extractedSeedDir,
177
+ extractedSeedGenxDir: dirs.extractedSeedGenxDir,
178
+ ...data,
179
+ };
180
+ }
181
+ };
182
+
183
+ const fetchGitSeed = async (seed: string, ref: string, dirs: SeedDirs, start: number) => {
184
+ const data = resolveGitSeed(seed, ref, dirs.subdir);
185
+ consola.info(
186
+ `Downloading Git seed ${bold(data.repo)} (Ref ${bold(data.ref)}, subdir: ${bold(
187
+ dirs.subdir || 'N/A',
188
+ )}) to ${bold(dirs.dir)} directory...`,
189
+ );
190
+ try {
191
+ await downloadTemplate(data.id, {
192
+ dir: dirs.extractedSeedDir, // destination directory to clone to
193
+ // auth?: string; // custom Authorization token to use for downloading template
194
+ registry: false, // disable built-in Giget registry
195
+ });
196
+ consola.success(`Seed downloaded in ${Date.now() - start}ms`);
197
+ return {
198
+ local: false,
199
+ genxDir: dirs.genxDir,
200
+ seed,
201
+ subdir: dirs.subdir,
202
+ pkg: loadGenxModule(dirs.extractedSeedGenxDir),
203
+ dir: dirs.dir,
204
+ extractedSeedDir: dirs.extractedSeedDir,
205
+ extractedSeedGenxDir: dirs.extractedSeedGenxDir,
206
+ ...data,
207
+ };
208
+ } catch (err) {
209
+ const error = process.env.DEBUG ? err : err.message;
210
+ consola.error(error);
211
+ process.exit(1);
212
+ }
213
+ };
214
+
215
+ // download remote or copy local seed
216
+ export const fetchSeed = async (
217
+ seed: string,
218
+ dir: string,
219
+ remote: boolean,
220
+ ref: string,
221
+ insecure: boolean,
222
+ subdir: string,
223
+ isInit: boolean,
224
+ lookupInNPM: boolean,
225
+ useDefaultNPMScope: boolean,
226
+ ): Promise<FetchResult> => {
227
+ // disable SSL cert verification
228
+ if (insecure) {
229
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
230
+ }
231
+
232
+ const start = Date.now();
233
+ const dirs = getSeedDirs(seed, dir, subdir, isInit, remote);
234
+
235
+ if (!isInit) {
236
+ await rm(dirs.extractedSeedDir);
237
+ mkdirSync(dirs.extractedSeedDir);
238
+ }
239
+
240
+ let result: FetchResult;
241
+ if (dirs.isLocalSeed) {
242
+ result = copyLocalSeed(dirs, ref, start);
243
+ } else {
244
+ if (lookupInNPM) {
245
+ result = await fetchNPMSeed(seed, ref, dirs, useDefaultNPMScope, start);
246
+ }
247
+ // if NPM lookup disabled or if it's enabled & failed, fall back to Git
248
+ if (!lookupInNPM || !result) {
249
+ result = await fetchGitSeed(seed, ref, dirs, start);
250
+ }
251
+ }
252
+
253
+ await removeExclusions(dirs.extractedSeedDir, result.pkg.genxSeedConfig?.exclude);
254
+
255
+ return result;
256
+ };
@@ -0,0 +1,34 @@
1
+ import { resolve } from 'node:path';
2
+ import { consola } from 'consola';
3
+ import { getPromptDefaults, getPromptValidators, validateSeedData } from './prompts';
4
+
5
+ // load seed details including prompt question defaults and validators
6
+ export const loadSeed = async (dir) => {
7
+ const GENX_RC = '.genxrc.js';
8
+ const genxConfigPath = resolve(dir, GENX_RC);
9
+ const { prompts, configure, details } = require(genxConfigPath);
10
+
11
+ const defaults = await getPromptDefaults(prompts());
12
+ const validators = await getPromptValidators(prompts());
13
+ const validate = (data) => validateSeedData(data, validators);
14
+ return {
15
+ prompts: prompts(),
16
+ configure: configure(),
17
+ details: details(),
18
+ defaults,
19
+ validators,
20
+ validate,
21
+ };
22
+ };
23
+
24
+ export const loadGenxModule = (genxDir: string, module = 'package.json', warnIfMissing = true) => {
25
+ const path = resolve(genxDir, module);
26
+ try {
27
+ return require(path);
28
+ } catch (e) {
29
+ if (warnIfMissing) {
30
+ consola.warn(`Could not load ${module} from ${genxDir}`);
31
+ }
32
+ return {};
33
+ }
34
+ };
@@ -0,0 +1,100 @@
1
+ import { existsSync, renameSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { consola } from 'consola';
4
+ import { bold } from './console';
5
+ import { listAllFiles, rm } from './fs';
6
+ import type { fsEntry } from './fs';
7
+ import { render, renderFile } from './handlebars';
8
+ import { writeJSON } from './json';
9
+
10
+ export const PROMPT_DATA_FILE = 'answers.json';
11
+
12
+ export const removeExclusions = async (dir, exclusions: string[]) => {
13
+ try {
14
+ const exclude = (exclusions ?? []).map((x) => resolve(dir, x));
15
+ await Promise.all(
16
+ exclude.map(async (exclusion) => {
17
+ if (existsSync(exclusion)) {
18
+ await rm(exclusion);
19
+ consola.debug(`Excluded: ${exclusion}`);
20
+ } else {
21
+ consola.debug(`Ignored invalid exclusion: ${exclusion}`);
22
+ }
23
+ }),
24
+ );
25
+ } catch (err) {
26
+ const error = process.env.DEBUG ? err : err.message;
27
+ consola.error(error);
28
+ process.exit(1);
29
+ }
30
+ };
31
+
32
+ const processItem = (
33
+ { base, dir: itemDir, path, isDir, ext, stats }: fsEntry,
34
+ data,
35
+ renamingEnabled: boolean,
36
+ interpolationEnabled: boolean,
37
+ interpolationWhitelist: string[],
38
+ ) => {
39
+ const baseNew = render(base, data);
40
+ const needsRenaming = renamingEnabled && baseNew !== base;
41
+ const pathNew = needsRenaming ? resolve(itemDir, baseNew) : path;
42
+
43
+ if (needsRenaming) {
44
+ try {
45
+ renameSync(resolve(itemDir, base), resolve(itemDir, baseNew));
46
+ consola.debug(`Renamed ${bold(base)} to ${bold(baseNew)}`);
47
+ } catch (e) {
48
+ consola.error(`Error renaming ${bold(base)} to ${bold(baseNew)}`);
49
+ }
50
+ }
51
+
52
+ // Handlebars rendering
53
+ if (interpolationEnabled && !isDir && interpolationWhitelist.some((i) => baseNew.includes(i))) {
54
+ try {
55
+ renderFile(pathNew, data, pathNew);
56
+ consola.debug(`Interpolated variables in ${pathNew}`);
57
+ } catch (e) {
58
+ consola.error(`Error interpolating variables in ${pathNew}`);
59
+ }
60
+ }
61
+
62
+ return { isDir, ext, dir: itemDir, base: baseNew, path: pathNew, stats };
63
+ };
64
+
65
+ export const processOptions = (fetchSeedResult, dir, data, isInit, persistedAnswers) => {
66
+ const options = fetchSeedResult.pkg?.genxSeedConfig || {};
67
+ consola.debug('Seed options', JSON.stringify(options));
68
+
69
+ if (options.processFiles) {
70
+ const ignoreDirs = options.processFiles?.exclude || [
71
+ '.git',
72
+ '.genx',
73
+ '.github',
74
+ 'node_modules',
75
+ '.gradle',
76
+ 'gradle',
77
+ ];
78
+
79
+ const filter = (entry: fsEntry) => !ignoreDirs.includes(entry.base);
80
+ const mapper = (entry: fsEntry) =>
81
+ processItem(
82
+ entry,
83
+ data,
84
+ !!options.processFiles?.rename,
85
+ !!options.processFiles?.interpolateVariables?.enabled,
86
+ options.processFiles?.interpolateVariables?.include,
87
+ );
88
+
89
+ listAllFiles(dir, filter, mapper);
90
+ }
91
+
92
+ if (options.persistAnswers) {
93
+ const jsonPath = resolve(fetchSeedResult.genxDir, PROMPT_DATA_FILE);
94
+ const additions = persistedAnswers.additions || [];
95
+ const json = isInit
96
+ ? data
97
+ : { ...data, appSeed: persistedAnswers.appSeed, additions: [...additions, data.appSeed] };
98
+ writeJSON(jsonPath, json);
99
+ }
100
+ };
@@ -0,0 +1,98 @@
1
+ import { cpSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { consola } from 'consola';
4
+ import { runAsync } from './child-process';
5
+ import { listAllFiles, rename, rm } from './fs';
6
+ import { renderFile } from './handlebars';
7
+ import { editJSONFile } from './json';
8
+ import { replaceObjectKeys } from './object';
9
+ import { getData } from './prompts';
10
+ import { loadGenxModule } from './seed.meta';
11
+ import { PROMPT_DATA_FILE, processOptions } from './seed.processing';
12
+
13
+ export const installSeedDeps = async (dir, isLogLevelVerbose) => {
14
+ let dependencies = {};
15
+ try {
16
+ const packageJSONPath = resolve(dir, 'package.json');
17
+ const packageJSON = require(packageJSONPath);
18
+ dependencies = packageJSON.dependencies || {};
19
+ } catch (e) {}
20
+
21
+ consola.debug('Seed NPM dependencies', {
22
+ targetInstallDir: dir,
23
+ dependencyCount: Object.keys(dependencies).length,
24
+ });
25
+
26
+ if (Object.keys(dependencies).length !== 0) {
27
+ const start2 = Date.now();
28
+ consola.info('Installing seed NPM dependencies...');
29
+ await runAsync(dir, 'npm install', {
30
+ prefixCommandWithNode: false,
31
+ exitOnError: false,
32
+ verbose: isLogLevelVerbose,
33
+ env: {
34
+ ...process.env,
35
+ NODE_ENV: 'production', // only install production NPM dependencies
36
+ },
37
+ }).catch((err) => {
38
+ consola.error(
39
+ 'Unexpected error installing seed dependencies. Try re-runing with --log-level verbose',
40
+ );
41
+ if (err) {
42
+ consola.error(err);
43
+ }
44
+ process.exit(1);
45
+ });
46
+ consola.success(`Installed in ${Date.now() - start2}ms`);
47
+ }
48
+ };
49
+
50
+ // run seed configure script
51
+ export const configureSeedApp = async (
52
+ seedInfo,
53
+ fetchSeedResult,
54
+ params,
55
+ dir,
56
+ skipOptionalPrompts,
57
+ isInit: boolean,
58
+ ) => {
59
+ const { configure, prompts, details, defaults, validate } = seedInfo;
60
+
61
+ // load persisted answers if available
62
+ const persistedAnswers = loadGenxModule(fetchSeedResult.genxDir, PROMPT_DATA_FILE, false);
63
+
64
+ // get prompt answers
65
+ const data = await getData(
66
+ persistedAnswers,
67
+ prompts,
68
+ details,
69
+ defaults,
70
+ validate,
71
+ params,
72
+ dir,
73
+ skipOptionalPrompts,
74
+ fetchSeedResult,
75
+ );
76
+ consola.debug('Loaded seed data', data);
77
+
78
+ const utils = {
79
+ writeFileWithData: renderFile,
80
+ editJSONFile,
81
+ replaceObjectKeys,
82
+ listAllFiles,
83
+ rename,
84
+ };
85
+
86
+ // run seed configure script
87
+ await configure(data, utils).catch((e) => consola.error(e));
88
+
89
+ // process seed exclusions, render templates, rename files/folders
90
+ processOptions(fetchSeedResult, fetchSeedResult.extractedSeedDir, data, isInit, persistedAnswers);
91
+
92
+ // when adding seed, copy result from temp folder to the app root (omitting .genx folder of the seed)
93
+ if (!isInit) {
94
+ await rm(fetchSeedResult.extractedSeedGenxDir);
95
+ cpSync(fetchSeedResult.extractedSeedDir, dir, { recursive: true });
96
+ await rm(fetchSeedResult.extractedSeedDir);
97
+ }
98
+ };
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ details: () => ({
3
+ nextStepsMessage: 'Thanks for installing mock seed'
4
+ }),
5
+ prompts: () => () => {},
6
+ configure: () => require('./configure'),
7
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = async (data, utils) => {
2
+ console.log('configure script');
3
+ };
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "mock-seed",
3
+ "description": "Mock Seed",
4
+ "version": "1.0.0",
5
+ "license": "Apache-2.0",
6
+ "genxSeedConfig": {
7
+ "processFiles": {
8
+ "interpolateVariables": {
9
+ "enabled": true,
10
+ "include": [
11
+ ".kt",
12
+ ".md"
13
+ ]
14
+ }
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,44 @@
1
+ ## Custom variable example
2
+
3
+ * Random: {{random}}
4
+ * Date: {{date}}
5
+
6
+ ## Env variable / conditional rendering example
7
+
8
+ {{#if foo}}
9
+ Env variable **FOO** set to true.
10
+ {{else}}
11
+ Env variable **FOO** set to false.
12
+ {{/if}}
13
+
14
+ https://handlebarsjs.com/examples/builtin-helper-ifelse-block.html
15
+
16
+ {{#unless foo}}
17
+ Env variable **FOO** wasn't set.
18
+ {{/unless}}
19
+
20
+ https://handlebarsjs.com/examples/builtin-helper-unless-block.html
21
+
22
+ ## Object
23
+
24
+ `obj.key` value: {{obj.key}}.
25
+
26
+ ## Loops
27
+
28
+ ### Literals
29
+
30
+ <ul>
31
+ {{#each arrLiterals}}
32
+ <li>{{this}}</li>
33
+ {{/each}}
34
+ </ul>
35
+
36
+ ### Objects
37
+
38
+ <ul>
39
+ {{#each arrObjects}}
40
+ <li>{{text}}</li>
41
+ {{/each}}
42
+ </ul>
43
+
44
+ https://handlebarsjs.com/examples/builtin-helper-each-block.html