@flarum/cli 1.0.0 → 2.0.0-beta.4

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 (186) 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/gitignore +13 -0
  12. package/boilerplate/skeleton/extension/js/admin.js +2 -1
  13. package/boilerplate/skeleton/extension/js/forum.js +2 -1
  14. package/boilerplate/skeleton/extension/js/package.json +13 -9
  15. package/boilerplate/skeleton/extension/js/src/admin/index.js +4 -2
  16. package/boilerplate/skeleton/extension/js/src/common/index.js +5 -0
  17. package/boilerplate/skeleton/extension/js/src/forum/index.js +4 -2
  18. package/boilerplate/skeleton/extension/js/tsconfig.json +6 -1
  19. package/boilerplate/skeleton/extension/locale/en.yml +1 -1
  20. package/boilerplate/stubs/migration.php +1 -1
  21. package/lib/base-command.d.ts +27 -6
  22. package/lib/base-command.js +131 -41
  23. package/lib/boilersmith/exposed-param-manager.d.ts +8 -0
  24. package/lib/boilersmith/exposed-param-manager.js +31 -0
  25. package/lib/boilersmith/io.d.ts +50 -0
  26. package/lib/boilersmith/io.js +72 -0
  27. package/lib/boilersmith/paths.d.ts +22 -0
  28. package/lib/boilersmith/paths.js +29 -0
  29. package/lib/boilersmith/scaffolding/audit-step-factory.d.ts +5 -0
  30. package/lib/boilersmith/scaffolding/audit-step-factory.js +54 -0
  31. package/lib/boilersmith/scaffolding/infra-step-factory.d.ts +5 -0
  32. package/lib/boilersmith/scaffolding/infra-step-factory.js +34 -0
  33. package/lib/boilersmith/scaffolding/init-step-factory.d.ts +5 -0
  34. package/lib/boilersmith/scaffolding/init-step-factory.js +34 -0
  35. package/lib/boilersmith/scaffolding/module.d.ts +73 -0
  36. package/lib/boilersmith/scaffolding/module.js +111 -0
  37. package/lib/boilersmith/scaffolding/scaffolder.d.ts +32 -0
  38. package/lib/boilersmith/scaffolding/scaffolder.js +203 -0
  39. package/lib/boilersmith/scaffolding/template-param.d.ts +23 -0
  40. package/lib/boilersmith/scaffolding/template-param.js +46 -0
  41. package/lib/boilersmith/step-manager.d.ts +85 -0
  42. package/lib/boilersmith/step-manager.js +234 -0
  43. package/lib/boilersmith/steps/stub-base.d.ts +53 -0
  44. package/lib/boilersmith/steps/stub-base.js +54 -0
  45. package/lib/{utils → boilersmith/utils}/clone-and-fill.d.ts +0 -0
  46. package/lib/{utils → boilersmith/utils}/clone-and-fill.js +2 -2
  47. package/lib/boilersmith/utils/cond-format.d.ts +1 -0
  48. package/lib/boilersmith/utils/cond-format.js +8 -0
  49. package/lib/boilersmith/utils/json-leaf-paths.d.ts +7 -0
  50. package/lib/boilersmith/utils/json-leaf-paths.js +17 -0
  51. package/lib/boilersmith/utils/read-tpl.d.ts +2 -0
  52. package/lib/boilersmith/utils/read-tpl.js +10 -0
  53. package/lib/boilersmith/utils/rename-keys.d.ts +1 -0
  54. package/lib/boilersmith/utils/rename-keys.js +7 -0
  55. package/lib/commands/audit/infra.d.ts +16 -0
  56. package/lib/commands/audit/infra.js +27 -0
  57. package/lib/commands/infra.d.ts +20 -0
  58. package/lib/commands/infra.js +55 -0
  59. package/lib/commands/init.d.ts +4 -3
  60. package/lib/commands/init.js +8 -6
  61. package/lib/commands/make/backend/api-controller.d.ts +4 -3
  62. package/lib/commands/make/backend/api-controller.js +3 -2
  63. package/lib/commands/make/backend/api-serializer-attributes.d.ts +4 -3
  64. package/lib/commands/make/backend/api-serializer-attributes.js +1 -1
  65. package/lib/commands/make/backend/api-serializer.d.ts +4 -3
  66. package/lib/commands/make/backend/api-serializer.js +3 -2
  67. package/lib/commands/make/backend/command.d.ts +4 -3
  68. package/lib/commands/make/backend/command.js +3 -2
  69. package/lib/commands/make/backend/event-listener.d.ts +4 -3
  70. package/lib/commands/make/backend/event-listener.js +3 -2
  71. package/lib/commands/make/backend/handler.d.ts +4 -3
  72. package/lib/commands/make/backend/handler.js +4 -3
  73. package/lib/commands/make/backend/integration-test.d.ts +4 -3
  74. package/lib/commands/make/backend/integration-test.js +3 -2
  75. package/lib/commands/make/backend/job.d.ts +4 -3
  76. package/lib/commands/make/backend/job.js +3 -2
  77. package/lib/commands/make/backend/migration.d.ts +4 -3
  78. package/lib/commands/make/backend/migration.js +3 -2
  79. package/lib/commands/make/backend/model.d.ts +4 -3
  80. package/lib/commands/make/backend/model.js +22 -21
  81. package/lib/commands/make/backend/policy.d.ts +4 -3
  82. package/lib/commands/make/backend/policy.js +3 -2
  83. package/lib/commands/make/backend/repository.d.ts +4 -3
  84. package/lib/commands/make/backend/repository.js +3 -2
  85. package/lib/commands/make/backend/route.d.ts +4 -3
  86. package/lib/commands/make/backend/route.js +1 -1
  87. package/lib/commands/make/backend/service-provider.d.ts +4 -3
  88. package/lib/commands/make/backend/service-provider.js +3 -2
  89. package/lib/commands/make/backend/validator.d.ts +4 -3
  90. package/lib/commands/make/backend/validator.js +3 -2
  91. package/lib/commands/make/frontend/component.d.ts +4 -3
  92. package/lib/commands/make/frontend/component.js +3 -2
  93. package/lib/commands/make/frontend/modal.d.ts +4 -3
  94. package/lib/commands/make/frontend/modal.js +3 -2
  95. package/lib/commands/make/frontend/model.d.ts +4 -3
  96. package/lib/commands/make/frontend/model.js +3 -2
  97. package/lib/commands/update/js-imports.d.ts +4 -3
  98. package/lib/commands/update/js-imports.js +1 -1
  99. package/lib/help.d.ts +5 -6
  100. package/lib/help.js +5 -4
  101. package/lib/index.d.ts +1 -1
  102. package/lib/index.js +3 -2
  103. package/lib/providers/index.d.ts +5 -0
  104. package/lib/providers/index.js +2 -0
  105. package/lib/{provider → providers}/php-provider.d.ts +0 -0
  106. package/lib/{provider → providers}/php-provider.js +2 -2
  107. package/lib/steps/extenders/api-serializer.js +2 -3
  108. package/lib/steps/extenders/base.d.ts +10 -10
  109. package/lib/steps/extenders/base.js +11 -11
  110. package/lib/steps/extenders/console-command.js +2 -3
  111. package/lib/steps/extenders/event.js +2 -3
  112. package/lib/steps/extenders/policy.js +2 -3
  113. package/lib/steps/extenders/route.js +2 -3
  114. package/lib/steps/extenders/service-provider.js +2 -3
  115. package/lib/steps/gen-ext-scaffolder.d.ts +6 -0
  116. package/lib/steps/gen-ext-scaffolder.js +510 -0
  117. package/lib/steps/js/base.d.ts +9 -9
  118. package/lib/steps/js/base.js +13 -12
  119. package/lib/steps/js/model.d.ts +5 -5
  120. package/lib/steps/js/model.js +8 -13
  121. package/lib/steps/misc/composer.d.ts +5 -6
  122. package/lib/steps/misc/composer.js +3 -3
  123. package/lib/steps/misc/git.d.ts +11 -0
  124. package/lib/steps/misc/git.js +20 -0
  125. package/lib/steps/misc/yarn.d.ts +5 -6
  126. package/lib/steps/misc/yarn.js +3 -3
  127. package/lib/steps/stubs/backend/api-controller.d.ts +1 -8
  128. package/lib/steps/stubs/backend/api-controller.js +2 -3
  129. package/lib/steps/stubs/backend/api-serializer.d.ts +4 -9
  130. package/lib/steps/stubs/backend/api-serializer.js +4 -4
  131. package/lib/steps/stubs/backend/command.d.ts +0 -5
  132. package/lib/steps/stubs/backend/command.js +1 -1
  133. package/lib/steps/stubs/backend/event-listener.d.ts +0 -5
  134. package/lib/steps/stubs/backend/event-listener.js +1 -1
  135. package/lib/steps/stubs/backend/handler-command.js +1 -1
  136. package/lib/steps/stubs/backend/handler.js +1 -1
  137. package/lib/steps/stubs/backend/migration.d.ts +4 -5
  138. package/lib/steps/stubs/backend/migration.js +8 -8
  139. package/lib/steps/stubs/backend/model.d.ts +4 -4
  140. package/lib/steps/stubs/backend/model.js +4 -4
  141. package/lib/steps/stubs/backend/policy.d.ts +0 -5
  142. package/lib/steps/stubs/backend/policy.js +1 -1
  143. package/lib/steps/stubs/backend/repository.d.ts +0 -5
  144. package/lib/steps/stubs/backend/repository.js +1 -1
  145. package/lib/steps/stubs/flarum-base.d.ts +9 -0
  146. package/lib/steps/stubs/flarum-base.js +10 -0
  147. package/lib/steps/stubs/frontend/component.js +0 -1
  148. package/lib/steps/stubs/frontend/modal.js +0 -1
  149. package/lib/steps/stubs/frontend/model.d.ts +4 -4
  150. package/lib/steps/stubs/frontend/model.js +3 -3
  151. package/lib/steps/stubs/js-base.d.ts +6 -7
  152. package/lib/steps/stubs/js-base.js +11 -16
  153. package/lib/steps/stubs/php-base.d.ts +7 -8
  154. package/lib/steps/stubs/php-base.js +12 -20
  155. package/lib/steps/update/js-imports.d.ts +5 -6
  156. package/lib/steps/update/js-imports.js +16 -19
  157. package/lib/utils/composer.d.ts +64 -0
  158. package/lib/utils/composer.js +8 -0
  159. package/lib/utils/migration.js +3 -3
  160. package/lib/utils/model-name.js +6 -6
  161. package/lib/utils/validation.d.ts +10 -7
  162. package/lib/utils/validation.js +9 -6
  163. package/oclif.manifest.json +1 -1
  164. package/package.json +55 -36
  165. package/php-subsystem/src/NodeVisitors/AddExtender.php +2 -1
  166. package/boilerplate/skeleton/extension/.github/workflows/build.yml +0 -32
  167. package/boilerplate/skeleton/extension/.github/workflows/lint.yml +0 -32
  168. package/boilerplate/skeleton/extension/.github/workflows/test.yml +0 -78
  169. package/lib/commands/infra/backend-testing.d.ts +0 -14
  170. package/lib/commands/infra/backend-testing.js +0 -26
  171. package/lib/provider/param-provider.d.ts +0 -15
  172. package/lib/provider/param-provider.js +0 -39
  173. package/lib/provider/path-provider.d.ts +0 -19
  174. package/lib/provider/path-provider.js +0 -24
  175. package/lib/steps/infra/backend-testing.d.ts +0 -8
  176. package/lib/steps/infra/backend-testing.js +0 -22
  177. package/lib/steps/infra/base.d.ts +0 -20
  178. package/lib/steps/infra/base.js +0 -36
  179. package/lib/steps/init/extension-skeleton.d.ts +0 -12
  180. package/lib/steps/init/extension-skeleton.js +0 -142
  181. package/lib/steps/step-manager.d.ts +0 -71
  182. package/lib/steps/step-manager.js +0 -173
  183. package/lib/steps/stubs/base.d.ts +0 -49
  184. package/lib/steps/stubs/base.js +0 -58
  185. package/lib/utils/extension-metadata.d.ts +0 -2
  186. package/lib/utils/extension-metadata.js +0 -21
@@ -0,0 +1,73 @@
1
+ import { Store } from 'mem-fs';
2
+ import { IO } from 'boilersmith/io';
3
+ import { Paths } from 'boilersmith/paths';
4
+ interface FileOwnership {
5
+ /**
6
+ * The path to the file
7
+ */
8
+ path: string;
9
+ /**
10
+ * Where the file should go. Defaults to path.
11
+ */
12
+ destPath?: string;
13
+ /**
14
+ * If any of the needed modules aren't enabled, the file won't be updated.
15
+ */
16
+ needsOtherModules?: string[];
17
+ /**
18
+ * If in a monorepo, should the file be placed relative to the monorepo root, and if so, where?
19
+ * If provided, takes priority over destPath.
20
+ */
21
+ monorepoPath?: string;
22
+ }
23
+ interface CommonModule<N extends string> {
24
+ name: N;
25
+ shortDescription: string;
26
+ longDescription?: string;
27
+ /**
28
+ * Whether files belonging to this module should be kept up to date.
29
+ */
30
+ updatable: boolean;
31
+ /**
32
+ * A list of scaffolding files managed by this module.
33
+ */
34
+ filesToReplace: (string | FileOwnership)[];
35
+ /**
36
+ * A map of names of JSON files to keys which should be deep-merged from the scaffolding.
37
+ */
38
+ jsonToAugment: Record<string, string[]>;
39
+ /**
40
+ * An array of names of template params needed by this module.
41
+ */
42
+ needsTemplateParams: string[];
43
+ }
44
+ interface UntoggleableModule<N extends string> extends CommonModule<N> {
45
+ /**
46
+ * Whether this module can be enabled/disabled.
47
+ */
48
+ togglable: false;
49
+ }
50
+ interface TogglableModule<N extends string> extends CommonModule<N> {
51
+ /**
52
+ * Whether this module can be enabled/disabled.
53
+ */
54
+ togglable: true;
55
+ /**
56
+ * Whether this module is enabled or disabled by default.
57
+ */
58
+ defaultEnabled: boolean;
59
+ /**
60
+ * Can only be enabled if these other modules are enabled.
61
+ */
62
+ dependsOn: string[];
63
+ }
64
+ export declare type Module<N extends string = string> = UntoggleableModule<N> | TogglableModule<N>;
65
+ export declare type ModuleStatusCache<N extends string> = {
66
+ get: (module: Module<N>, fs: Store, paths: Paths) => Promise<boolean | undefined>;
67
+ set: (module: Module<N>, val: boolean, fs: Store, paths: Paths) => Promise<void>;
68
+ };
69
+ export declare function promptModulesEnabled<N extends string>(modules: Module<N>[], io: IO): Promise<Record<N, boolean>>;
70
+ export declare function currModulesEnabled<N extends string>(modules: Module<N>[], fs: Store, paths: Paths, cache?: ModuleStatusCache<N>): Promise<Record<N, boolean>>;
71
+ export declare function setModuleValue<MN extends string>(module: Module<MN>, enabled: boolean, fs: Store, paths: Paths, cache: ModuleStatusCache<MN>): Promise<void>;
72
+ export declare function applyModule<MN extends string, TN extends string>(module: Module<MN>, modulesEnabled: Record<string, boolean>, paramVals: Record<TN, unknown>, scaffoldDir: string, fs: Store, paths: Paths, excludeFiles?: string[], isInitial?: boolean): Promise<Store>;
73
+ export {};
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyModule = exports.setModuleValue = exports.currModulesEnabled = exports.promptModulesEnabled = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
6
+ const mem_fs_editor_1 = require("mem-fs-editor");
7
+ const node_path_1 = require("node:path");
8
+ const pick_deep_1 = (0, tslib_1.__importDefault)(require("pick-deep"));
9
+ const read_tpl_1 = require("boilersmith/utils/read-tpl");
10
+ const clone_and_fill_1 = require("boilersmith/utils/clone-and-fill");
11
+ const rename_keys_1 = require("boilersmith/utils/rename-keys");
12
+ const cond_format_1 = require("boilersmith/utils/cond-format");
13
+ async function promptModulesEnabled(modules, io) {
14
+ const modulesEnabled = {};
15
+ const advanced = await io.getParam({
16
+ name: 'advancedInstallation',
17
+ type: 'confirm',
18
+ initial: false,
19
+ message: `Advanced Initialization ${(0, cond_format_1.condFormat)(io.supportsAnsiColor, chalk_1.default.dim, '(fine-tune which features are enabled)')}`,
20
+ });
21
+ for (const m of modules) {
22
+ const missingDeps = !m.togglable || m.dependsOn.some(dep => !modulesEnabled[dep]);
23
+ if (!m.togglable) {
24
+ modulesEnabled[m.name] = true;
25
+ }
26
+ else if (missingDeps) {
27
+ modulesEnabled[m.name] = false;
28
+ }
29
+ else if (advanced) {
30
+ // eslint-disable-next-line no-await-in-loop
31
+ modulesEnabled[m.name] = await io.getParam({
32
+ name: `modules.${m.name}`,
33
+ type: 'confirm',
34
+ initial: m.defaultEnabled,
35
+ message: m.shortDescription + (m.longDescription ? (0, cond_format_1.condFormat)(io.supportsAnsiColor, chalk_1.default.dim, ` (${m.longDescription})`) : ''),
36
+ });
37
+ }
38
+ else {
39
+ modulesEnabled[m.name] = m.defaultEnabled;
40
+ }
41
+ }
42
+ return modulesEnabled;
43
+ }
44
+ exports.promptModulesEnabled = promptModulesEnabled;
45
+ async function currModulesEnabled(modules, fs, paths, cache) {
46
+ const modulesEnabled = {};
47
+ for (const m of modules) {
48
+ if (m.togglable) {
49
+ // eslint-disable-next-line no-await-in-loop
50
+ const cacheVal = await (cache === null || cache === void 0 ? void 0 : cache.get(m, fs, paths));
51
+ modulesEnabled[m.name] = cacheVal !== null && cacheVal !== void 0 ? cacheVal : m.defaultEnabled;
52
+ }
53
+ else {
54
+ modulesEnabled[m.name] = true;
55
+ }
56
+ }
57
+ return modulesEnabled;
58
+ }
59
+ exports.currModulesEnabled = currModulesEnabled;
60
+ async function setModuleValue(module, enabled, fs, paths, cache) {
61
+ if (module.togglable) {
62
+ cache.set(module, enabled, fs, paths);
63
+ }
64
+ }
65
+ exports.setModuleValue = setModuleValue;
66
+ // eslint-disable-next-line max-params
67
+ async function applyModule(module, modulesEnabled, paramVals, scaffoldDir, fs, paths, excludeFiles = [], isInitial = false) {
68
+ var _a;
69
+ const fsEditor = (0, mem_fs_editor_1.create)(fs);
70
+ // Validate that module can be enabled
71
+ if (!(modulesEnabled === null || modulesEnabled === void 0 ? void 0 : modulesEnabled[module.name])) {
72
+ throw new Error(`Could not apply module "${module.name}", because it is not enabled in the provided module statuses.`);
73
+ }
74
+ // Validate that dependencies are enabled
75
+ const missingDeps = module.togglable ? module.dependsOn.filter(dep => !modulesEnabled[dep]) : [];
76
+ if (missingDeps.length > 0) {
77
+ throw new Error(`Could not apply module "${module.name}", because the following dependency modules are missing: "${missingDeps.join(', ')}".`);
78
+ }
79
+ // Validate that all needed params are present
80
+ const missingParams = module.needsTemplateParams.filter(p => !(p in paramVals));
81
+ if (missingParams.length > 0) {
82
+ throw new Error(`Could not apply module "${module.name}", because the following params are missing: "${missingParams.join(', ')}".`);
83
+ }
84
+ if (!isInitial && !module.updatable) {
85
+ throw new Error(`Cannot update module "${module.name}", as it is not updatable, and has already been initialized.`);
86
+ }
87
+ const tplData = {
88
+ params: paramVals,
89
+ modules: modulesEnabled,
90
+ };
91
+ 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}`));
92
+ for (const file of module.filesToReplace) {
93
+ const path = typeof file === 'string' ? file : file.path;
94
+ const needsOtherModules = typeof file === 'string' ? [] : (_a = file.needsOtherModules) !== null && _a !== void 0 ? _a : [];
95
+ if (!excludeFiles.includes(path) && !needsOtherModules.some(dep => !modulesEnabled[dep])) {
96
+ const copyToIfMonorepo = typeof file !== 'string' && file.monorepoPath ? paths.monorepo((0, clone_and_fill_1.cloneAndFill)(file.monorepoPath, tplDataFlat)) : undefined;
97
+ const copyTo = copyToIfMonorepo !== null && copyToIfMonorepo !== void 0 ? copyToIfMonorepo : paths.package(typeof file !== 'string' && file.destPath ? (0, clone_and_fill_1.cloneAndFill)(file.destPath, tplDataFlat) : path);
98
+ fsEditor.copyTpl((0, node_path_1.resolve)(scaffoldDir, path), copyTo, tplData);
99
+ }
100
+ }
101
+ const jsonPaths = (0, clone_and_fill_1.cloneAndFill)(module.jsonToAugment, tplDataFlat);
102
+ for (const jsonPath of Object.keys(jsonPaths)) {
103
+ const scaffoldContents = (0, read_tpl_1.readTpl)((0, node_path_1.resolve)(scaffoldDir, jsonPath), tplData);
104
+ const scaffoldContentsJson = JSON.parse(scaffoldContents);
105
+ const fieldsToAugment = jsonPaths[jsonPath];
106
+ const relevant = (0, pick_deep_1.default)(scaffoldContentsJson, fieldsToAugment);
107
+ fsEditor.extendJSON(paths.package(jsonPath), relevant, undefined, 4);
108
+ }
109
+ return fs;
110
+ }
111
+ exports.applyModule = applyModule;
@@ -0,0 +1,32 @@
1
+ import { Store } from 'mem-fs';
2
+ import { Paths } from '../paths';
3
+ import { DefaultProviders, Step } from '../step-manager';
4
+ import { Module, ModuleStatusCache } from './module';
5
+ import { TemplateParam } from './template-param';
6
+ import { IO } from 'boilersmith/io';
7
+ export declare type ExcludeScaffoldingFunc = (fs: Store, paths: Paths) => string[];
8
+ export declare class Scaffolder<TN extends string = string, MN extends string = string> {
9
+ private templateParams;
10
+ private modules;
11
+ private scaffoldDir;
12
+ private moduleStatusCache?;
13
+ private excludeScaffoldingFunc?;
14
+ constructor(scaffoldDir: string);
15
+ constructor(scaffoldDir: string, moduleStatusCache: ModuleStatusCache<MN>);
16
+ constructor(scaffoldDir: string, moduleStatusCache: ModuleStatusCache<MN>, excludeScaffoldingFuncs: ExcludeScaffoldingFunc);
17
+ registerModule(module: Module<MN>): this;
18
+ registerTemplateParam<T>(templateParam: TemplateParam<T, TN>): this;
19
+ genInitStep<Providers extends DefaultProviders>(): Step<Providers>;
20
+ genInfraStep<Providers extends DefaultProviders>(module: string): Step<Providers>;
21
+ genAuditStep<Providers extends DefaultProviders>(dry?: boolean): Step<Providers>;
22
+ templateParamVal<T>(param: TN, fs: Store, paths: Paths, io: IO): Promise<T>;
23
+ templateParamVals(fs: Store, paths: Paths, io: IO): Promise<Record<TN, unknown>>;
24
+ modulesEnabled(fs: Store, paths: Paths): Promise<Record<MN, boolean>>;
25
+ moduleFiles(moduleName: MN): string[];
26
+ /**
27
+ * Confirm that all files and configuration keys in the scaffolding dir
28
+ * are owned by at least one module, and that no modules own nonexistent
29
+ * files or configuration keys.
30
+ */
31
+ validate(): Promise<void>;
32
+ }
@@ -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 {};