@flarum/cli 1.0.0 → 2.0.0-beta.1

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 (185) hide show
  1. package/README.md +2 -2
  2. package/bin/run +5 -3
  3. package/boilerplate/skeleton/extension/.gitattributes +16 -3
  4. package/boilerplate/skeleton/extension/.github/workflows/backend.yml +15 -0
  5. package/boilerplate/skeleton/extension/.github/workflows/frontend.yml +20 -0
  6. package/boilerplate/skeleton/extension/.styleci.yml +14 -0
  7. package/boilerplate/skeleton/extension/LICENSE.md +1 -0
  8. package/boilerplate/skeleton/extension/README.md +7 -7
  9. package/boilerplate/skeleton/extension/composer.json +11 -9
  10. package/boilerplate/skeleton/extension/extend.php +10 -10
  11. package/boilerplate/skeleton/extension/js/admin.js +2 -1
  12. package/boilerplate/skeleton/extension/js/forum.js +2 -1
  13. package/boilerplate/skeleton/extension/js/package.json +13 -9
  14. package/boilerplate/skeleton/extension/js/src/admin/index.js +4 -2
  15. package/boilerplate/skeleton/extension/js/src/common/index.js +5 -0
  16. package/boilerplate/skeleton/extension/js/src/forum/index.js +4 -2
  17. package/boilerplate/skeleton/extension/js/tsconfig.json +6 -1
  18. package/boilerplate/skeleton/extension/locale/en.yml +1 -1
  19. package/boilerplate/stubs/migration.php +1 -1
  20. package/lib/base-command.d.ts +27 -6
  21. package/lib/base-command.js +131 -41
  22. package/lib/boilersmith/exposed-param-manager.d.ts +8 -0
  23. package/lib/boilersmith/exposed-param-manager.js +31 -0
  24. package/lib/boilersmith/io.d.ts +50 -0
  25. package/lib/boilersmith/io.js +72 -0
  26. package/lib/boilersmith/paths.d.ts +22 -0
  27. package/lib/boilersmith/paths.js +29 -0
  28. package/lib/boilersmith/scaffolding/audit-step-factory.d.ts +5 -0
  29. package/lib/boilersmith/scaffolding/audit-step-factory.js +54 -0
  30. package/lib/boilersmith/scaffolding/infra-step-factory.d.ts +5 -0
  31. package/lib/boilersmith/scaffolding/infra-step-factory.js +34 -0
  32. package/lib/boilersmith/scaffolding/init-step-factory.d.ts +5 -0
  33. package/lib/boilersmith/scaffolding/init-step-factory.js +34 -0
  34. package/lib/boilersmith/scaffolding/module.d.ts +68 -0
  35. package/lib/boilersmith/scaffolding/module.js +111 -0
  36. package/lib/boilersmith/scaffolding/scaffolder.d.ts +32 -0
  37. package/lib/boilersmith/scaffolding/scaffolder.js +203 -0
  38. package/lib/boilersmith/scaffolding/template-param.d.ts +23 -0
  39. package/lib/boilersmith/scaffolding/template-param.js +46 -0
  40. package/lib/boilersmith/step-manager.d.ts +85 -0
  41. package/lib/boilersmith/step-manager.js +234 -0
  42. package/lib/boilersmith/steps/stub-base.d.ts +53 -0
  43. package/lib/boilersmith/steps/stub-base.js +54 -0
  44. package/lib/{utils → boilersmith/utils}/clone-and-fill.d.ts +0 -0
  45. package/lib/{utils → boilersmith/utils}/clone-and-fill.js +2 -2
  46. package/lib/boilersmith/utils/cond-format.d.ts +1 -0
  47. package/lib/boilersmith/utils/cond-format.js +8 -0
  48. package/lib/boilersmith/utils/json-leaf-paths.d.ts +7 -0
  49. package/lib/boilersmith/utils/json-leaf-paths.js +17 -0
  50. package/lib/boilersmith/utils/read-tpl.d.ts +2 -0
  51. package/lib/boilersmith/utils/read-tpl.js +10 -0
  52. package/lib/boilersmith/utils/rename-keys.d.ts +1 -0
  53. package/lib/boilersmith/utils/rename-keys.js +7 -0
  54. package/lib/commands/audit/infra.d.ts +16 -0
  55. package/lib/commands/audit/infra.js +27 -0
  56. package/lib/commands/infra.d.ts +20 -0
  57. package/lib/commands/infra.js +55 -0
  58. package/lib/commands/init.d.ts +4 -3
  59. package/lib/commands/init.js +8 -6
  60. package/lib/commands/make/backend/api-controller.d.ts +4 -3
  61. package/lib/commands/make/backend/api-controller.js +3 -2
  62. package/lib/commands/make/backend/api-serializer-attributes.d.ts +4 -3
  63. package/lib/commands/make/backend/api-serializer-attributes.js +1 -1
  64. package/lib/commands/make/backend/api-serializer.d.ts +4 -3
  65. package/lib/commands/make/backend/api-serializer.js +3 -2
  66. package/lib/commands/make/backend/command.d.ts +4 -3
  67. package/lib/commands/make/backend/command.js +3 -2
  68. package/lib/commands/make/backend/event-listener.d.ts +4 -3
  69. package/lib/commands/make/backend/event-listener.js +3 -2
  70. package/lib/commands/make/backend/handler.d.ts +4 -3
  71. package/lib/commands/make/backend/handler.js +4 -3
  72. package/lib/commands/make/backend/integration-test.d.ts +4 -3
  73. package/lib/commands/make/backend/integration-test.js +3 -2
  74. package/lib/commands/make/backend/job.d.ts +4 -3
  75. package/lib/commands/make/backend/job.js +3 -2
  76. package/lib/commands/make/backend/migration.d.ts +4 -3
  77. package/lib/commands/make/backend/migration.js +3 -2
  78. package/lib/commands/make/backend/model.d.ts +4 -3
  79. package/lib/commands/make/backend/model.js +22 -21
  80. package/lib/commands/make/backend/policy.d.ts +4 -3
  81. package/lib/commands/make/backend/policy.js +3 -2
  82. package/lib/commands/make/backend/repository.d.ts +4 -3
  83. package/lib/commands/make/backend/repository.js +3 -2
  84. package/lib/commands/make/backend/route.d.ts +4 -3
  85. package/lib/commands/make/backend/route.js +1 -1
  86. package/lib/commands/make/backend/service-provider.d.ts +4 -3
  87. package/lib/commands/make/backend/service-provider.js +3 -2
  88. package/lib/commands/make/backend/validator.d.ts +4 -3
  89. package/lib/commands/make/backend/validator.js +3 -2
  90. package/lib/commands/make/frontend/component.d.ts +4 -3
  91. package/lib/commands/make/frontend/component.js +3 -2
  92. package/lib/commands/make/frontend/modal.d.ts +4 -3
  93. package/lib/commands/make/frontend/modal.js +3 -2
  94. package/lib/commands/make/frontend/model.d.ts +4 -3
  95. package/lib/commands/make/frontend/model.js +3 -2
  96. package/lib/commands/update/js-imports.d.ts +4 -3
  97. package/lib/commands/update/js-imports.js +1 -1
  98. package/lib/help.d.ts +5 -6
  99. package/lib/help.js +5 -4
  100. package/lib/index.d.ts +1 -1
  101. package/lib/index.js +3 -2
  102. package/lib/providers/index.d.ts +5 -0
  103. package/lib/providers/index.js +2 -0
  104. package/lib/{provider → providers}/php-provider.d.ts +0 -0
  105. package/lib/{provider → providers}/php-provider.js +2 -2
  106. package/lib/steps/extenders/api-serializer.js +2 -3
  107. package/lib/steps/extenders/base.d.ts +10 -10
  108. package/lib/steps/extenders/base.js +11 -11
  109. package/lib/steps/extenders/console-command.js +2 -3
  110. package/lib/steps/extenders/event.js +2 -3
  111. package/lib/steps/extenders/policy.js +2 -3
  112. package/lib/steps/extenders/route.js +2 -3
  113. package/lib/steps/extenders/service-provider.js +2 -3
  114. package/lib/steps/gen-ext-scaffolder.d.ts +6 -0
  115. package/lib/steps/gen-ext-scaffolder.js +508 -0
  116. package/lib/steps/js/base.d.ts +9 -9
  117. package/lib/steps/js/base.js +11 -11
  118. package/lib/steps/js/model.d.ts +5 -5
  119. package/lib/steps/js/model.js +8 -13
  120. package/lib/steps/misc/composer.d.ts +5 -6
  121. package/lib/steps/misc/composer.js +3 -3
  122. package/lib/steps/misc/git.d.ts +11 -0
  123. package/lib/steps/misc/git.js +20 -0
  124. package/lib/steps/misc/yarn.d.ts +5 -6
  125. package/lib/steps/misc/yarn.js +3 -3
  126. package/lib/steps/stubs/backend/api-controller.d.ts +1 -8
  127. package/lib/steps/stubs/backend/api-controller.js +2 -3
  128. package/lib/steps/stubs/backend/api-serializer.d.ts +4 -9
  129. package/lib/steps/stubs/backend/api-serializer.js +4 -4
  130. package/lib/steps/stubs/backend/command.d.ts +0 -5
  131. package/lib/steps/stubs/backend/command.js +1 -1
  132. package/lib/steps/stubs/backend/event-listener.d.ts +0 -5
  133. package/lib/steps/stubs/backend/event-listener.js +1 -1
  134. package/lib/steps/stubs/backend/handler-command.js +1 -1
  135. package/lib/steps/stubs/backend/handler.js +1 -1
  136. package/lib/steps/stubs/backend/migration.d.ts +4 -5
  137. package/lib/steps/stubs/backend/migration.js +8 -8
  138. package/lib/steps/stubs/backend/model.d.ts +4 -4
  139. package/lib/steps/stubs/backend/model.js +4 -4
  140. package/lib/steps/stubs/backend/policy.d.ts +0 -5
  141. package/lib/steps/stubs/backend/policy.js +1 -1
  142. package/lib/steps/stubs/backend/repository.d.ts +0 -5
  143. package/lib/steps/stubs/backend/repository.js +1 -1
  144. package/lib/steps/stubs/flarum-base.d.ts +9 -0
  145. package/lib/steps/stubs/flarum-base.js +10 -0
  146. package/lib/steps/stubs/frontend/component.js +0 -1
  147. package/lib/steps/stubs/frontend/modal.js +0 -1
  148. package/lib/steps/stubs/frontend/model.d.ts +4 -4
  149. package/lib/steps/stubs/frontend/model.js +3 -3
  150. package/lib/steps/stubs/js-base.d.ts +6 -7
  151. package/lib/steps/stubs/js-base.js +11 -16
  152. package/lib/steps/stubs/php-base.d.ts +7 -8
  153. package/lib/steps/stubs/php-base.js +12 -20
  154. package/lib/steps/update/js-imports.d.ts +5 -6
  155. package/lib/steps/update/js-imports.js +13 -17
  156. package/lib/utils/composer.d.ts +64 -0
  157. package/lib/utils/composer.js +8 -0
  158. package/lib/utils/migration.js +3 -3
  159. package/lib/utils/model-name.js +6 -6
  160. package/lib/utils/validation.d.ts +10 -7
  161. package/lib/utils/validation.js +9 -6
  162. package/oclif.manifest.json +1 -1
  163. package/package.json +53 -31
  164. package/php-subsystem/src/NodeVisitors/AddExtender.php +2 -1
  165. package/boilerplate/skeleton/extension/.github/workflows/build.yml +0 -32
  166. package/boilerplate/skeleton/extension/.github/workflows/lint.yml +0 -32
  167. package/boilerplate/skeleton/extension/.github/workflows/test.yml +0 -78
  168. package/lib/commands/infra/backend-testing.d.ts +0 -14
  169. package/lib/commands/infra/backend-testing.js +0 -26
  170. package/lib/provider/param-provider.d.ts +0 -15
  171. package/lib/provider/param-provider.js +0 -39
  172. package/lib/provider/path-provider.d.ts +0 -19
  173. package/lib/provider/path-provider.js +0 -24
  174. package/lib/steps/infra/backend-testing.d.ts +0 -8
  175. package/lib/steps/infra/backend-testing.js +0 -22
  176. package/lib/steps/infra/base.d.ts +0 -20
  177. package/lib/steps/infra/base.js +0 -36
  178. package/lib/steps/init/extension-skeleton.d.ts +0 -12
  179. package/lib/steps/init/extension-skeleton.js +0 -142
  180. package/lib/steps/step-manager.d.ts +0 -71
  181. package/lib/steps/step-manager.js +0 -173
  182. package/lib/steps/stubs/base.d.ts +0 -49
  183. package/lib/steps/stubs/base.js +0 -58
  184. package/lib/utils/extension-metadata.d.ts +0 -2
  185. package/lib/utils/extension-metadata.js +0 -21
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scaffolder = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const globby_1 = (0, tslib_1.__importDefault)(require("globby"));
6
+ const node_path_1 = require("node:path");
7
+ const json_leaf_paths_1 = require("boilersmith/utils/json-leaf-paths");
8
+ const read_tpl_1 = require("boilersmith/utils/read-tpl");
9
+ const init_step_factory_1 = require("./init-step-factory");
10
+ const module_1 = require("./module");
11
+ const template_param_1 = require("./template-param");
12
+ const infra_step_factory_1 = require("./infra-step-factory");
13
+ const rename_keys_1 = require("boilersmith/utils/rename-keys");
14
+ const clone_and_fill_1 = require("boilersmith/utils/clone-and-fill");
15
+ const audit_step_factory_1 = require("./audit-step-factory");
16
+ class Scaffolder {
17
+ constructor(scaffoldDir, moduleStatusCache, excludeScaffoldingFunc) {
18
+ this.templateParams = [];
19
+ this.modules = [];
20
+ this.scaffoldDir = scaffoldDir;
21
+ this.moduleStatusCache = moduleStatusCache;
22
+ this.excludeScaffoldingFunc = excludeScaffoldingFunc;
23
+ }
24
+ registerModule(module) {
25
+ this.modules.push(module);
26
+ return this;
27
+ }
28
+ registerTemplateParam(templateParam) {
29
+ if ((0, template_param_1.isComputedParam)(templateParam)) {
30
+ const currParams = new Set(this.templateParams.map(p => (0, template_param_1.getParamName)(p)));
31
+ const missingDeps = templateParam.uses.filter(dep => !currParams.has(dep));
32
+ if (missingDeps.length > 0) {
33
+ throw new Error(`Computed template param "${(0, template_param_1.getParamName)(templateParam)}" is missing dependency params: "${missingDeps.join(', ')}".`);
34
+ }
35
+ }
36
+ this.templateParams.push(templateParam);
37
+ return this;
38
+ }
39
+ genInitStep() {
40
+ return (0, init_step_factory_1.initStepFactory)(this.scaffoldDir, this.modules, this.templateParams, this.excludeScaffoldingFunc, this.moduleStatusCache);
41
+ }
42
+ genInfraStep(module) {
43
+ return (0, infra_step_factory_1.infraStepFactory)(this.scaffoldDir, module, this.modules, this.templateParams, this.excludeScaffoldingFunc, this.moduleStatusCache);
44
+ }
45
+ genAuditStep(dry = true) {
46
+ return (0, audit_step_factory_1.auditStepFactory)(dry, this.scaffoldDir, this.modules, this.templateParams, this.excludeScaffoldingFunc, this.moduleStatusCache);
47
+ }
48
+ async templateParamVal(param, fs, paths, io) {
49
+ return (await (0, template_param_1.currParamValues)(this.templateParams, fs, paths, io))[param];
50
+ }
51
+ async templateParamVals(fs, paths, io) {
52
+ return (0, template_param_1.currParamValues)(this.templateParams, fs, paths, io);
53
+ }
54
+ async modulesEnabled(fs, paths) {
55
+ return (0, module_1.currModulesEnabled)(this.modules, fs, paths, this.moduleStatusCache);
56
+ }
57
+ moduleFiles(moduleName) {
58
+ var _a, _b;
59
+ return (_b = (_a = this.modules.find(m => m.name === moduleName)) === null || _a === void 0 ? void 0 : _a.filesToReplace.map(f => typeof f === 'string' ? f : f.path)) !== null && _b !== void 0 ? _b : [];
60
+ }
61
+ /**
62
+ * Confirm that all files and configuration keys in the scaffolding dir
63
+ * are owned by at least one module, and that no modules own nonexistent
64
+ * files or configuration keys.
65
+ */
66
+ async validate() {
67
+ const errors = [];
68
+ // Generate template data for future checks
69
+ const paramVals = Object.fromEntries(this.templateParams.map(param => [(0, template_param_1.getParamName)(param), 'TEST']));
70
+ const modulesEnabled = Object.fromEntries(this.modules.map(module => [module.name, true]));
71
+ const tplData = { params: paramVals, modules: modulesEnabled };
72
+ const tplDataFlat = Object.assign(Object.assign({}, (0, rename_keys_1.renameKeys)(tplData.modules, k => `modules.${k}`)), (0, rename_keys_1.renameKeys)(tplData.params, k => `params.${k}`));
73
+ const moduleNames = new Set(this.modules.map(m => m.name));
74
+ for (const module of this.modules) {
75
+ const missingDeps = module.togglable ? module.dependsOn.filter(dep => !moduleNames.has(dep)) : [];
76
+ if (missingDeps.length > 0) {
77
+ errors.push(`Module "${module.name}" depends on modules that are not registered: "${missingDeps.join(', ')}".`);
78
+ }
79
+ }
80
+ const filesToOwnerModules = new Map();
81
+ const configKeysToOwnerModules = new Map();
82
+ const templateParamsToUsingModules = new Map();
83
+ for (const module of this.modules) {
84
+ module.filesToReplace.forEach(file => {
85
+ var _a;
86
+ const path = typeof file === 'string' ? file : file.path;
87
+ const currModules = (_a = filesToOwnerModules.get(path)) !== null && _a !== void 0 ? _a : [];
88
+ filesToOwnerModules.set(path, [...currModules, module.name]);
89
+ });
90
+ module.needsTemplateParams.forEach(paramName => {
91
+ var _a;
92
+ const currModules = (_a = filesToOwnerModules.get(paramName)) !== null && _a !== void 0 ? _a : [];
93
+ templateParamsToUsingModules.set(paramName, [...currModules, module.name]);
94
+ });
95
+ Object.keys(module.jsonToAugment).forEach(jsonPath => {
96
+ var _a;
97
+ const ownedKeys = (0, clone_and_fill_1.cloneAndFill)(module.jsonToAugment, tplDataFlat)[jsonPath];
98
+ const currJsonData = (_a = configKeysToOwnerModules.get(jsonPath)) !== null && _a !== void 0 ? _a : { fileOwners: [], keyOwners: new Map() };
99
+ currJsonData.fileOwners.push(module.name);
100
+ ownedKeys.forEach(key => {
101
+ var _a;
102
+ const currModules = (_a = currJsonData.keyOwners.get(key)) !== null && _a !== void 0 ? _a : [];
103
+ currJsonData.keyOwners.set(key, [...currModules, module.name]);
104
+ });
105
+ configKeysToOwnerModules.set(jsonPath, currJsonData);
106
+ });
107
+ }
108
+ // Ensure that there's no interesection between owned regular files and files with owned JSON keys.
109
+ const moduleOwnedFiles = [...filesToOwnerModules.keys()];
110
+ const moduleOwnedJsonConfs = new Set(configKeysToOwnerModules.keys());
111
+ moduleOwnedFiles
112
+ .filter(path => moduleOwnedJsonConfs.has(path))
113
+ .forEach(path => {
114
+ var _a, _b;
115
+ const fileModules = (_a = filesToOwnerModules.get(path)) === null || _a === void 0 ? void 0 : _a.join(', ');
116
+ const keyModules = (_b = configKeysToOwnerModules.get(path)) === null || _b === void 0 ? void 0 : _b.fileOwners.join(', ');
117
+ errors.push(`File "${path}" is owned by modules: "${fileModules}". However, it also has keys that are owned by modules: "${keyModules}".`);
118
+ });
119
+ // Ensure that all owned files exist in the filesystem.
120
+ const scaffoldingFilePaths = new Set((await (0, globby_1.default)((0, node_path_1.resolve)(this.scaffoldDir, '**/*'), { dot: true })).map(p => p.replace(`${this.scaffoldDir}/`, '')).filter(p => p !== ''));
121
+ moduleOwnedFiles
122
+ .filter(path => !scaffoldingFilePaths.has(path))
123
+ .forEach(path => {
124
+ var _a;
125
+ const fileModules = (_a = filesToOwnerModules.get(path)) === null || _a === void 0 ? void 0 : _a.join(', ');
126
+ errors.push(`File "${path}" is owned by modules: "${fileModules}", but it doesn't exist in the scaffolding directory.`);
127
+ });
128
+ [...moduleOwnedJsonConfs]
129
+ .filter(path => !scaffoldingFilePaths.has(path))
130
+ .forEach(path => {
131
+ var _a;
132
+ const keyModules = (_a = configKeysToOwnerModules.get(path)) === null || _a === void 0 ? void 0 : _a.fileOwners.join(', ');
133
+ errors.push(`File "${path}" has keys owned by modules: "${keyModules}", but it doesn't exist in the scaffolding directory.`);
134
+ });
135
+ // Ensure that all files in the filesystem are owned by at least one module.
136
+ [...scaffoldingFilePaths]
137
+ .filter(path => !filesToOwnerModules.has(path) && !configKeysToOwnerModules.has(path))
138
+ .forEach(path => {
139
+ errors.push(`File "${path}" is not owned by any module.`);
140
+ });
141
+ // Ensure that every template param is used by at least one module.
142
+ this.templateParams
143
+ .filter(p => !templateParamsToUsingModules.has((0, template_param_1.getParamName)(p)))
144
+ .forEach(p => {
145
+ errors.push(`Template param "${(0, template_param_1.getParamName)(p)}" is defined, but not used by any modules.`);
146
+ });
147
+ // Ensure that every module's needed template params are provided.
148
+ const providedParams = new Set(this.templateParams.map(p => (0, template_param_1.getParamName)(p)));
149
+ [...templateParamsToUsingModules.keys()]
150
+ .filter(paramName => !providedParams.has(paramName))
151
+ .forEach(paramName => {
152
+ var _a;
153
+ const paramModules = (_a = templateParamsToUsingModules.get(paramName)) === null || _a === void 0 ? void 0 : _a.join(', ');
154
+ errors.push(`Template param "${paramName}" is used by modules: "${paramModules}", but is not provided.`);
155
+ });
156
+ // Ensure that for owned JSON files, all keys are owned by at least one module.
157
+ [...configKeysToOwnerModules.entries()]
158
+ .filter(([path]) => scaffoldingFilePaths.has(path))
159
+ .forEach(([path, ownerData]) => {
160
+ // Flush pending errors before readTpl calls, because
161
+ // if that fails due to templating syntax issues, no other
162
+ // errors will be shared, and those other errors commonly
163
+ // are the cause of template errors (e.g. missing params).
164
+ if (errors.length > 0) {
165
+ throw new Error(errors.join('\n'));
166
+ }
167
+ const contents = (0, read_tpl_1.readTpl)((0, node_path_1.resolve)(this.scaffoldDir, path), tplData);
168
+ let json;
169
+ try {
170
+ json = JSON.parse(contents);
171
+ const keys = new Set((0, json_leaf_paths_1.jsonLeafPaths)(json));
172
+ [...keys]
173
+ .filter(k => !ownerData.keyOwners.has(k))
174
+ .forEach(k => {
175
+ errors.push(`Key "${k}" in file "${path}" is not owned by any module.`);
176
+ });
177
+ [...ownerData.keyOwners.entries()]
178
+ .filter(([k]) => !keys.has(k))
179
+ .forEach(([k, owners]) => {
180
+ errors.push(`Key "${k}" is owned by modules: "${owners.join(', ')}", but does not exist in file "${path}".`);
181
+ });
182
+ }
183
+ catch (error) {
184
+ if (error instanceof SyntaxError) {
185
+ errors.push(`File "${path}" is invalid JSON: ${error.message}`);
186
+ }
187
+ else {
188
+ errors.push(String(error));
189
+ }
190
+ }
191
+ });
192
+ // Flush pending errors again, as if we run into problems during `readTpl`,
193
+ // we won't be able to express other errors.
194
+ if (errors.length > 0) {
195
+ throw new Error(errors.join('\n'));
196
+ }
197
+ // Ensure that all files can be resolved with ejs using provided template variables.
198
+ scaffoldingFilePaths.forEach(path => {
199
+ (0, read_tpl_1.readTpl)((0, node_path_1.resolve)(this.scaffoldDir, path), tplData);
200
+ });
201
+ }
202
+ }
203
+ exports.Scaffolder = Scaffolder;
@@ -0,0 +1,23 @@
1
+ import { Store } from 'mem-fs';
2
+ import { ParamDef, IO } from 'boilersmith/io';
3
+ import { Paths } from '../paths';
4
+ interface PromptTemplateParam<T, N extends string = string> {
5
+ /**
6
+ * A config object to prompt for the param's value.
7
+ * Also contains the param name.
8
+ */
9
+ prompt: ParamDef<N>;
10
+ getCurrVal: (fs: Store, paths: Paths) => Promise<T | undefined>;
11
+ }
12
+ interface ComputedTemplateParam<T, N extends string = string> {
13
+ name: N;
14
+ uses: string[];
15
+ compute: (paths: Paths, ...args: any[]) => Promise<T>;
16
+ }
17
+ export declare type TemplateParam<T = unknown, N extends string = string> = PromptTemplateParam<T, N> | ComputedTemplateParam<T, N>;
18
+ export declare function isPromptParam<T, N extends string>(param: TemplateParam<T, N>): param is PromptTemplateParam<T, N>;
19
+ export declare function isComputedParam<T, N extends string>(param: TemplateParam<T, N>): param is ComputedTemplateParam<T, N>;
20
+ export declare function getParamName<T, N extends string>(param: TemplateParam<T, N>): N;
21
+ export declare function promptParamValues(params: TemplateParam[], paths: Paths, io: IO): Promise<Record<string, unknown>>;
22
+ export declare function currParamValues(params: TemplateParam[], fs: Store, paths: Paths, io: IO): Promise<Record<string, unknown>>;
23
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.currParamValues = exports.promptParamValues = exports.getParamName = exports.isComputedParam = exports.isPromptParam = void 0;
4
+ function isPromptParam(param) {
5
+ return 'prompt' in param;
6
+ }
7
+ exports.isPromptParam = isPromptParam;
8
+ function isComputedParam(param) {
9
+ return 'uses' in param;
10
+ }
11
+ exports.isComputedParam = isComputedParam;
12
+ function getParamName(param) {
13
+ return isComputedParam(param) ? param.name : param.prompt.name;
14
+ }
15
+ exports.getParamName = getParamName;
16
+ async function withComputedParamValues(params, paths, paramVals) {
17
+ const vals = Object.assign({}, paramVals);
18
+ const computedParams = params.filter(isComputedParam);
19
+ for (const p of computedParams) {
20
+ const depValues = p.uses.map(key => vals[key]);
21
+ // eslint-disable-next-line no-await-in-loop
22
+ vals[p.name] = await p.compute(paths, ...depValues);
23
+ }
24
+ return vals;
25
+ }
26
+ async function promptParamValues(params, paths, io) {
27
+ const promptParams = params.filter(isPromptParam);
28
+ const paramVals = {};
29
+ for (const p of promptParams) {
30
+ // eslint-disable-next-line no-await-in-loop
31
+ paramVals[p.prompt.name] = await io.getParam(p.prompt);
32
+ }
33
+ return withComputedParamValues(params, paths, paramVals);
34
+ }
35
+ exports.promptParamValues = promptParamValues;
36
+ async function currParamValues(params, fs, paths, io) {
37
+ var _a;
38
+ const promptParams = params.filter(isPromptParam);
39
+ const paramVals = {};
40
+ for (const p of promptParams) {
41
+ // eslint-disable-next-line no-await-in-loop
42
+ paramVals[p.prompt.name] = (_a = await p.getCurrVal(fs, paths)) !== null && _a !== void 0 ? _a : await io.getParam(p.prompt);
43
+ }
44
+ return withComputedParamValues(params, paths, paramVals);
45
+ }
46
+ exports.currParamValues = currParamValues;
@@ -0,0 +1,85 @@
1
+ import { Store } from 'mem-fs';
2
+ import { ExposedParamManager } from './exposed-param-manager';
3
+ import { IO, Message } from './io';
4
+ import { Paths } from './paths';
5
+ export declare type DefaultProviders = {};
6
+ export interface Step<Providers extends DefaultProviders = DefaultProviders, Exposes extends string = string> {
7
+ /**
8
+ * A short string describing what the step does.
9
+ */
10
+ type: string;
11
+ /**
12
+ * Can this step be composed with other steps?
13
+ *
14
+ * This is the case if the step makes no changes to the disk filesystem while running.
15
+ */
16
+ composable: boolean;
17
+ /**
18
+ * A list of names of params that this step exposes to other steps.
19
+ */
20
+ exposes: Exposes[];
21
+ run: (fs: Store, paths: Paths, io: IO, providers: Providers) => Promise<Store>;
22
+ /**
23
+ * Return an object of exposed params.
24
+ *
25
+ * The paths and io will be the same objects provided to the `run` method.
26
+ */
27
+ getExposed(paths: Paths, io: IO): Record<Exposes, unknown>;
28
+ }
29
+ interface ShouldRunConfig {
30
+ optional?: boolean;
31
+ confirmationMessage?: string;
32
+ default?: boolean;
33
+ }
34
+ interface StoredStep<Providers extends DefaultProviders> {
35
+ name?: string;
36
+ step: Step<Providers>;
37
+ shouldRun: ShouldRunConfig;
38
+ dependencies: StepDependency[];
39
+ predefinedParams: PredefinedParameters;
40
+ mapPaths: string[];
41
+ }
42
+ interface StepDependency {
43
+ sourceStep: string;
44
+ exposedName: string;
45
+ consumedName?: string;
46
+ dontRunIfFalsy?: boolean;
47
+ modifier?: (value: unknown) => string;
48
+ }
49
+ declare type PredefinedParameters = Record<string, unknown>;
50
+ declare type StepsResult = {
51
+ succeeded: true;
52
+ messages: Message[];
53
+ stepsRan: string[];
54
+ } | {
55
+ succeeded: false;
56
+ error: string;
57
+ errorTrace?: string;
58
+ messages: Message[];
59
+ stepsRan: string[];
60
+ };
61
+ export declare class StepManager<Providers extends DefaultProviders> {
62
+ protected steps: Array<StoredStep<Providers> | AtomicStepManager<Providers>>;
63
+ protected namedSteps: Map<string, StoredStep<Providers>>;
64
+ protected exposedParams: ExposedParamManager;
65
+ /**
66
+ * A step is an incremental operation that updates the filesystem.
67
+ */
68
+ step(step: Step<Providers>, shouldRun?: ShouldRunConfig, dependencies?: StepDependency[], predefinedParams?: PredefinedParameters, mapPaths?: string[]): this;
69
+ namedStep(name: string, step: Step<Providers>, shouldRun?: ShouldRunConfig, dependencies?: StepDependency[], predefinedParams?: PredefinedParameters, mapPaths?: string[]): this;
70
+ atomicGroup(callback: (stepManager: AtomicStepManager<Providers>) => void): this;
71
+ protected validateDependencies(step: Step<Providers>, dependencies: StepDependency[], mapPaths: string[]): void;
72
+ run(paths: Paths, io: IO, providers: Providers, dry?: boolean): Promise<StepsResult>;
73
+ protected stepShouldRun(storedStep: StoredStep<Providers>, io: IO, packagePath?: string): Promise<boolean>;
74
+ protected runStep(storedStep: StoredStep<Providers>, paths: Paths, io: IO, providers: Providers, packagePath?: string, fs?: Store): Promise<Store>;
75
+ protected commit(fs: Store): Promise<boolean>;
76
+ }
77
+ export declare class AtomicStepManager<Providers = DefaultProviders> extends StepManager<Providers> {
78
+ protected steps: StoredStep<Providers>[];
79
+ constructor(parentNamedSteps?: Map<string, StoredStep<Providers>>, parentExposedParams?: ExposedParamManager);
80
+ step(step: Step<Providers>, shouldRun?: ShouldRunConfig, dependencies?: StepDependency[], predefinedParams?: PredefinedParameters, mapPaths?: string[]): this;
81
+ namedStep(name: string, step: Step<Providers>, shouldRun?: ShouldRunConfig, dependencies?: StepDependency[], predefinedParams?: PredefinedParameters, mapPaths?: string[]): this;
82
+ atomicGroup(_callback: (stepManager: AtomicStepManager<Providers>) => void): this;
83
+ run(paths: Paths, io: IO, providers: Providers, dry?: boolean): Promise<StepsResult>;
84
+ }
85
+ export {};
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AtomicStepManager = exports.StepManager = void 0;
4
+ /* eslint-disable no-await-in-loop */
5
+ const mem_fs_1 = require("mem-fs");
6
+ const mem_fs_editor_1 = require("mem-fs-editor");
7
+ const exposed_param_manager_1 = require("./exposed-param-manager");
8
+ const formatDependencies = (strings) => strings.map(s => `"${s}"`).join(', ');
9
+ class StepManager {
10
+ constructor() {
11
+ this.steps = [];
12
+ this.namedSteps = new Map();
13
+ this.exposedParams = new exposed_param_manager_1.ExposedParamManager();
14
+ }
15
+ /**
16
+ * A step is an incremental operation that updates the filesystem.
17
+ */
18
+ step(step, shouldRun = {}, dependencies = [], predefinedParams = {}, mapPaths = []) {
19
+ this.validateDependencies(step, dependencies, mapPaths);
20
+ this.steps = [...this.steps, { step, shouldRun, dependencies, predefinedParams, mapPaths }];
21
+ return this;
22
+ }
23
+ namedStep(name, step, shouldRun = {}, dependencies = [], predefinedParams = {}, mapPaths = []) {
24
+ if (this.namedSteps.has(name)) {
25
+ throw new Error(`Named steps must have unique names. A step with name "${name}" already exists.`);
26
+ }
27
+ this.validateDependencies(step, dependencies, mapPaths);
28
+ const newStep = { name, step, shouldRun, dependencies, predefinedParams, mapPaths };
29
+ this.steps = [...this.steps, newStep];
30
+ this.namedSteps.set(name, newStep);
31
+ return this;
32
+ }
33
+ atomicGroup(callback) {
34
+ const atomicCollection = new AtomicStepManager(this.namedSteps, this.exposedParams);
35
+ callback(atomicCollection);
36
+ this.steps = [...this.steps, atomicCollection];
37
+ return this;
38
+ }
39
+ validateDependencies(step, dependencies, mapPaths) {
40
+ const missingDependencySteps = dependencies.map(dep => dep.sourceStep).filter(stepName => !this.namedSteps.has(stepName));
41
+ if (missingDependencySteps.length > 0) {
42
+ throw new Error(`Step of type "${step.type}" depends on nonexistent named steps ${formatDependencies(missingDependencySteps)}`);
43
+ }
44
+ const missingDependencyParams = [];
45
+ const missingDependencyParamSteps = [];
46
+ for (const dependency of dependencies) {
47
+ const sourceStep = this.namedSteps.get(dependency.sourceStep);
48
+ if (!(sourceStep === null || sourceStep === void 0 ? void 0 : sourceStep.step.exposes.includes(dependency.exposedName)) && dependency.exposedName !== '__succeeded') {
49
+ missingDependencyParamSteps.push(sourceStep === null || sourceStep === void 0 ? void 0 : sourceStep.name);
50
+ missingDependencyParams.push(dependency.exposedName);
51
+ }
52
+ if (mapPaths.length === 0 && (sourceStep === null || sourceStep === void 0 ? void 0 : sourceStep.mapPaths.length)) {
53
+ throw new Error(`Non path-mapped step of type "${step.type}" may not depend on path-mapped step "${sourceStep === null || sourceStep === void 0 ? void 0 : sourceStep.name}".`);
54
+ }
55
+ const missingMapPathsDeps = mapPaths.filter(p => sourceStep && !sourceStep.mapPaths.includes(p));
56
+ if (sourceStep && (missingMapPathsDeps === null || missingMapPathsDeps === void 0 ? void 0 : missingMapPathsDeps.length) && sourceStep.mapPaths.length > 0) {
57
+ throw new Error(`Step of type "${step.type}" (A) depends on named step: "${sourceStep.name}" (B), but is mapped across some paths that (B) is not: "${missingMapPathsDeps.join(', ')}"`);
58
+ }
59
+ }
60
+ if (missingDependencyParamSteps.length > 0) {
61
+ throw new Error(`Step of type "${step.type}" depends on nonexistent exposed params ${formatDependencies(missingDependencyParams)} from named steps ${formatDependencies(missingDependencyParamSteps)}`);
62
+ }
63
+ }
64
+ async run(paths, io, providers, dry = false) {
65
+ if (dry && this.steps.some(s => !(s instanceof AtomicStepManager) && !s.step.composable)) {
66
+ throw new Error('Cannot dry run, as this step manager has non-composable steps.');
67
+ }
68
+ const stepNames = [];
69
+ const checkAndRun = async (step, packagePath) => {
70
+ const shouldRun = await this.stepShouldRun(step, io, packagePath);
71
+ if (!shouldRun)
72
+ return;
73
+ const fs = await this.runStep(step, paths, io, providers, packagePath);
74
+ if (!dry) {
75
+ await this.commit(fs);
76
+ }
77
+ stepNames.push(packagePath ? `${step.step.type} (${packagePath})` : step.step.type);
78
+ };
79
+ try {
80
+ for (const storedStep of this.steps) {
81
+ if (storedStep instanceof AtomicStepManager) {
82
+ const res = await storedStep.run(paths, io, providers);
83
+ if (!res.succeeded) {
84
+ throw new Error(res.error);
85
+ }
86
+ stepNames.push(...res.stepsRan);
87
+ }
88
+ else if (storedStep.mapPaths.length > 0) {
89
+ for (const path of storedStep.mapPaths) {
90
+ await checkAndRun(storedStep, path);
91
+ }
92
+ }
93
+ else {
94
+ await checkAndRun(storedStep);
95
+ }
96
+ }
97
+ }
98
+ catch (error) {
99
+ return {
100
+ succeeded: false,
101
+ error: error instanceof Error ? error.message : String(error),
102
+ errorTrace: error instanceof Error ? error.stack : undefined,
103
+ stepsRan: stepNames,
104
+ messages: io.getOutput(),
105
+ };
106
+ }
107
+ return {
108
+ succeeded: true,
109
+ stepsRan: stepNames,
110
+ messages: io.getOutput(),
111
+ };
112
+ }
113
+ async stepShouldRun(storedStep, io, packagePath) {
114
+ let allDependenciesRan = true;
115
+ let noRequiredNonFalsyDependenciesAreFalsy = true;
116
+ for (const dep of storedStep.dependencies) {
117
+ const sourceStep = this.namedSteps.get(dep.sourceStep);
118
+ const sourcePackagePath = (sourceStep === null || sourceStep === void 0 ? void 0 : sourceStep.mapPaths.length) ? packagePath : undefined;
119
+ if (!this.exposedParams.stepRan(dep.sourceStep, sourcePackagePath))
120
+ allDependenciesRan = false;
121
+ if (dep.exposedName !== '__succeeded' && dep.dontRunIfFalsy && !this.exposedParams.get(dep.sourceStep, dep.exposedName, sourcePackagePath)) {
122
+ noRequiredNonFalsyDependenciesAreFalsy = false;
123
+ }
124
+ }
125
+ if (!allDependenciesRan || !noRequiredNonFalsyDependenciesAreFalsy)
126
+ return false;
127
+ if (!storedStep.shouldRun.optional)
128
+ return true;
129
+ const promptConfirm = await io.newInstance({ context: 'Confirm Step' }, []).getParam({
130
+ name: 'execute_step',
131
+ message: storedStep.shouldRun.confirmationMessage || `Run step of type "${storedStep.step.type}"?`,
132
+ initial: storedStep.shouldRun.default || false,
133
+ type: 'confirm',
134
+ });
135
+ return promptConfirm;
136
+ }
137
+ async runStep(storedStep, paths, io, providers, packagePath, fs = (0, mem_fs_1.create)()) {
138
+ const initial = storedStep.dependencies.reduce((initial, dep) => {
139
+ const sourceStep = this.namedSteps.get(dep.sourceStep);
140
+ let depValue;
141
+ depValue =
142
+ dep.exposedName === '__succeeded' ?
143
+ this.exposedParams.stepRan(dep.sourceStep, packagePath) :
144
+ this.exposedParams.get(dep.sourceStep, dep.exposedName, (sourceStep === null || sourceStep === void 0 ? void 0 : sourceStep.mapPaths.length) ? packagePath : undefined);
145
+ if (dep.modifier) {
146
+ depValue = dep.modifier(depValue);
147
+ }
148
+ initial[dep.consumedName || dep.exposedName] = depValue;
149
+ return initial;
150
+ }, {});
151
+ const cloned = io.newInstance(Object.assign(Object.assign({}, initial), storedStep.predefinedParams), io.getOutput());
152
+ const stepPaths = packagePath ? paths.onMonorepoSub(packagePath) : paths;
153
+ const newFs = await storedStep.step.run(fs, stepPaths, cloned, providers);
154
+ if (storedStep.name) {
155
+ this.exposedParams.add(storedStep.name, storedStep.step.getExposed(stepPaths, io), packagePath);
156
+ }
157
+ return newFs;
158
+ }
159
+ async commit(fs) {
160
+ return new Promise((resolve, _reject) => {
161
+ (0, mem_fs_editor_1.create)(fs).commit(err => {
162
+ if (err) {
163
+ throw new Error(err);
164
+ }
165
+ resolve(true);
166
+ });
167
+ });
168
+ }
169
+ }
170
+ exports.StepManager = StepManager;
171
+ class AtomicStepManager extends StepManager {
172
+ constructor(parentNamedSteps = new Map(), parentExposedParams = new exposed_param_manager_1.ExposedParamManager()) {
173
+ super();
174
+ this.steps = [];
175
+ this.namedSteps = parentNamedSteps;
176
+ this.exposedParams = parentExposedParams;
177
+ }
178
+ step(step, shouldRun = {}, dependencies = [], predefinedParams = {}, mapPaths = []) {
179
+ if (!step.composable) {
180
+ throw new Error(`Step of type "${step.type}" is not composable, and cannot be added to an atomic group.`);
181
+ }
182
+ return super.step(step, shouldRun, dependencies, predefinedParams, mapPaths);
183
+ }
184
+ namedStep(name, step, shouldRun = {}, dependencies = [], predefinedParams = {}, mapPaths = []) {
185
+ if (!step.composable) {
186
+ throw new Error(`Step of type "${step.type}" is not composable, and cannot be added to an atomic group.`);
187
+ }
188
+ return super.namedStep(name, step, shouldRun, dependencies, predefinedParams, mapPaths);
189
+ }
190
+ atomicGroup(_callback) {
191
+ throw new Error("Atomic groups can't be nested.");
192
+ }
193
+ async run(paths, io, providers, dry = false) {
194
+ let fs = (0, mem_fs_1.create)();
195
+ const checkAndRun = async (step, packagePath) => {
196
+ const shouldRun = await this.stepShouldRun(step, io, packagePath);
197
+ if (!shouldRun)
198
+ return;
199
+ fs = await this.runStep(step, paths, io, providers, packagePath, fs);
200
+ stepNames.push(packagePath ? `${step.step.type} (${packagePath})` : step.step.type);
201
+ };
202
+ const stepNames = [];
203
+ try {
204
+ for (const storedStep of this.steps) {
205
+ if (storedStep.mapPaths.length > 0) {
206
+ for (const path of storedStep.mapPaths) {
207
+ await checkAndRun(storedStep, path);
208
+ }
209
+ }
210
+ else {
211
+ await checkAndRun(storedStep);
212
+ }
213
+ }
214
+ }
215
+ catch (error) {
216
+ return {
217
+ error: error instanceof Error ? error.message : String(error),
218
+ errorTrace: error instanceof Error ? error.stack : undefined,
219
+ succeeded: false,
220
+ stepsRan: [],
221
+ messages: io.getOutput(),
222
+ };
223
+ }
224
+ if (!dry) {
225
+ await this.commit(fs);
226
+ }
227
+ return {
228
+ succeeded: true,
229
+ stepsRan: stepNames,
230
+ messages: io.getOutput(),
231
+ };
232
+ }
233
+ }
234
+ exports.AtomicStepManager = AtomicStepManager;
@@ -0,0 +1,53 @@
1
+ import { Store } from 'mem-fs';
2
+ import { ParamDef, IO } from 'boilersmith/io';
3
+ import { Paths } from 'boilersmith/paths';
4
+ import { DefaultProviders, Step } from 'boilersmith/step-manager';
5
+ import { Scaffolder } from 'boilersmith/scaffolding/scaffolder';
6
+ interface UserProvidedParam extends Omit<ParamDef, 'type'> {
7
+ type: string;
8
+ }
9
+ export interface StubGenerationSchema {
10
+ /**
11
+ * A period-delimited subdirectory for where the stub should be
12
+ * located relative to the package root (IE `schema.root`).
13
+ */
14
+ recommendedSubdir: string;
15
+ /**
16
+ * Create the file in the recommended subdir regardless
17
+ * of the current requested dir.
18
+ */
19
+ forceRecommendedSubdir?: boolean;
20
+ /**
21
+ * Where should the file be created in relation to the extension root?
22
+ *
23
+ * Defaults to `./src`.
24
+ */
25
+ root?: string;
26
+ /**
27
+ * The relative path to the stub's source file relative to the
28
+ * `stubs` directory.
29
+ */
30
+ sourceFile: string;
31
+ params: UserProvidedParam[];
32
+ }
33
+ export declare abstract class BaseStubStep<Providers extends DefaultProviders, PN extends string = string, MN extends string = string> implements Step<Providers> {
34
+ protected stubDir: string;
35
+ protected scaffolder: Scaffolder<PN, MN>;
36
+ constructor(stubDir: string, scaffolder: Scaffolder<PN, MN>);
37
+ abstract type: string;
38
+ protected abstract defaultRoot: string;
39
+ protected abstract schema: StubGenerationSchema;
40
+ composable: boolean;
41
+ get exposes(): string[];
42
+ protected additionalExposes: string[];
43
+ get implicitParams(): string[];
44
+ protected additionalImplicitParams: string[];
45
+ protected params: Record<string, unknown>;
46
+ protected subdir: string;
47
+ run(fs: Store, paths: Paths, io: IO, _providers: Providers): Promise<Store>;
48
+ getExposed(_paths: Paths, _paramProvider: IO): Record<string, unknown>;
49
+ protected precompileParams(_fs: Store, _paths: Paths, _paramProvider: IO): Promise<Record<string, unknown>>;
50
+ protected compileParams(fs: Store, paths: Paths, io: IO): Promise<Record<string, unknown>>;
51
+ protected abstract getFileName(_fs: Store, _paths: Paths, io: IO): Promise<string>;
52
+ }
53
+ export {};