@form8ion/javascript 15.6.0 → 15.6.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 (257) hide show
  1. package/package.json +4 -3
  2. package/src/code-style/index.js +3 -0
  3. package/src/code-style/lifter.js +6 -0
  4. package/src/code-style/lifter.test.js +25 -0
  5. package/src/code-style/remark/index.js +3 -0
  6. package/src/code-style/remark/lifter.js +10 -0
  7. package/src/code-style/remark/lifter.test.js +28 -0
  8. package/src/code-style/remark/scaffolder.js +38 -0
  9. package/src/code-style/remark/scaffolder.test.js +102 -0
  10. package/src/code-style/remark/tester.js +11 -0
  11. package/src/code-style/remark/tester.test.js +46 -0
  12. package/src/code-style/scaffolder.js +26 -0
  13. package/src/code-style/scaffolder.test.js +87 -0
  14. package/src/code-style/tester.js +5 -0
  15. package/src/code-style/tester.test.js +23 -0
  16. package/src/corepack/index.js +1 -0
  17. package/src/corepack/lifter.js +5 -0
  18. package/src/corepack/lifter.test.js +22 -0
  19. package/src/coverage/index.js +3 -0
  20. package/src/coverage/lifter.js +31 -0
  21. package/src/coverage/lifter.test.js +58 -0
  22. package/src/coverage/nyc/index.js +2 -0
  23. package/src/coverage/nyc/remover.js +16 -0
  24. package/src/coverage/nyc/remover.test.js +24 -0
  25. package/src/coverage/nyc/tester.js +5 -0
  26. package/src/coverage/nyc/tester.test.js +29 -0
  27. package/src/coverage/scaffolder.js +7 -0
  28. package/src/coverage/scaffolder.test.js +32 -0
  29. package/src/coverage/tester.js +10 -0
  30. package/src/coverage/tester.test.js +50 -0
  31. package/src/dependencies/index.js +3 -0
  32. package/src/dependencies/installer.js +25 -0
  33. package/src/dependencies/installer.test.js +77 -0
  34. package/src/dependencies/package-managers.js +32 -0
  35. package/src/dependencies/package-managers.test.js +46 -0
  36. package/src/dependencies/processor.js +30 -0
  37. package/src/dependencies/processor.test.js +75 -0
  38. package/src/dependencies/remover.js +10 -0
  39. package/src/dependencies/remover.test.js +28 -0
  40. package/src/dialects/babel/config/ignore-adder.js +10 -0
  41. package/src/dialects/babel/config/ignore-adder.test.js +33 -0
  42. package/src/dialects/babel/config/index.js +3 -0
  43. package/src/dialects/babel/config/loader.js +5 -0
  44. package/src/dialects/babel/config/loader.test.js +21 -0
  45. package/src/dialects/babel/config/writer.js +6 -0
  46. package/src/dialects/babel/config/writer.test.js +20 -0
  47. package/src/dialects/babel/index.js +3 -0
  48. package/src/dialects/babel/lifter.js +7 -0
  49. package/src/dialects/babel/lifter.test.js +17 -0
  50. package/src/dialects/babel/predicate.js +5 -0
  51. package/src/dialects/babel/predicate.test.js +25 -0
  52. package/src/dialects/babel/scaffolder.js +14 -0
  53. package/src/dialects/babel/scaffolder.test.js +28 -0
  54. package/src/dialects/index.js +2 -0
  55. package/src/dialects/prompt-choices.js +10 -0
  56. package/src/dialects/prompt-choices.test.js +28 -0
  57. package/src/dialects/scaffolder.js +15 -0
  58. package/src/dialects/scaffolder.test.js +49 -0
  59. package/src/dialects/typescript/index.js +1 -0
  60. package/src/dialects/typescript/scaffolder.js +31 -0
  61. package/src/dialects/typescript/scaffolder.test.js +95 -0
  62. package/src/documentation/generation-command.js +11 -0
  63. package/src/documentation/generation-command.test.js +25 -0
  64. package/src/documentation/index.js +1 -0
  65. package/src/documentation/scaffolder.js +20 -0
  66. package/src/documentation/scaffolder.test.js +49 -0
  67. package/src/engines/index.js +2 -0
  68. package/src/engines/lifter.js +7 -0
  69. package/src/engines/lifter.test.js +18 -0
  70. package/src/engines/tester.js +7 -0
  71. package/src/engines/tester.test.js +37 -0
  72. package/src/index.js +9 -0
  73. package/src/lifter.js +55 -0
  74. package/src/lifter.test.js +96 -0
  75. package/src/linting/index.js +1 -0
  76. package/src/linting/scaffolder.js +5 -0
  77. package/src/linting/scaffolder.test.js +31 -0
  78. package/src/lockfile-lint/allowed-hosts-builder.js +6 -0
  79. package/src/lockfile-lint/allowed-hosts-builder.test.js +35 -0
  80. package/src/lockfile-lint/config.js +12 -0
  81. package/src/lockfile-lint/config.test.js +37 -0
  82. package/src/lockfile-lint/index.js +3 -0
  83. package/src/lockfile-lint/scaffolder.js +38 -0
  84. package/src/lockfile-lint/scaffolder.test.js +85 -0
  85. package/src/lockfile-lint/tester.js +5 -0
  86. package/src/lockfile-lint/tester.test.js +25 -0
  87. package/src/node-version/index.js +2 -0
  88. package/src/node-version/scaffolder.js +19 -0
  89. package/src/node-version/scaffolder.test.js +33 -0
  90. package/src/node-version/tasks.js +25 -0
  91. package/src/node-version/tasks.test.js +43 -0
  92. package/src/node-version/tester.js +5 -0
  93. package/src/node-version/tester.test.js +29 -0
  94. package/src/npm-config/index.js +5 -0
  95. package/src/npm-config/lifter.js +14 -0
  96. package/src/npm-config/lifter.test.js +23 -0
  97. package/src/npm-config/reader.js +11 -0
  98. package/src/npm-config/reader.test.js +33 -0
  99. package/src/npm-config/scaffolder.js +16 -0
  100. package/src/npm-config/scaffolder.test.js +54 -0
  101. package/src/npm-config/tester.js +5 -0
  102. package/src/npm-config/tester.test.js +29 -0
  103. package/src/npm-config/writer.js +6 -0
  104. package/src/npm-config/writer.test.js +24 -0
  105. package/src/options/schemas.js +14 -0
  106. package/src/options/schemas.test.js +147 -0
  107. package/src/options/validator.js +45 -0
  108. package/src/options/validator.test.js +79 -0
  109. package/src/package/details.js +18 -0
  110. package/src/package/details.test.js +51 -0
  111. package/src/package/index.js +2 -0
  112. package/src/package/lifter.js +47 -0
  113. package/src/package/lifter.test.js +100 -0
  114. package/src/package/package-name.js +13 -0
  115. package/src/package/package-name.test.js +52 -0
  116. package/src/package/property-sorter.js +38 -0
  117. package/src/package/property-sorter.test.js +56 -0
  118. package/src/package/scaffolder.js +32 -0
  119. package/src/package/scaffolder.test.js +46 -0
  120. package/src/package/scripts/index.js +1 -0
  121. package/src/package/scripts/lifter.js +14 -0
  122. package/src/package/scripts/lifter.test.js +31 -0
  123. package/src/package/scripts/script-comparator.js +46 -0
  124. package/src/package/scripts/script-comparator.test.js +119 -0
  125. package/src/package/scripts/scripts-sorter.js +7 -0
  126. package/src/package/scripts/scripts-sorter.test.js +20 -0
  127. package/src/package/scripts/test-script-updater.js +15 -0
  128. package/src/package/scripts/test-script-updater.test.js +32 -0
  129. package/src/package/vcs-host-details.js +12 -0
  130. package/src/package/vcs-host-details.test.js +16 -0
  131. package/src/package-managers/current-manager-resolver.js +21 -0
  132. package/src/package-managers/current-manager-resolver.test.js +51 -0
  133. package/src/package-managers/index.js +5 -0
  134. package/src/package-managers/lifter.js +7 -0
  135. package/src/package-managers/lifter.test.js +18 -0
  136. package/src/package-managers/lockfile-path-resolver.js +10 -0
  137. package/src/package-managers/lockfile-path-resolver.test.js +15 -0
  138. package/src/package-managers/npm/index.js +2 -0
  139. package/src/package-managers/npm/scaffolder.js +19 -0
  140. package/src/package-managers/npm/scaffolder.test.js +33 -0
  141. package/src/package-managers/npm/tester.js +11 -0
  142. package/src/package-managers/npm/tester.test.js +33 -0
  143. package/src/package-managers/scaffolder.js +11 -0
  144. package/src/package-managers/scaffolder.test.js +27 -0
  145. package/src/package-managers/tester.js +11 -0
  146. package/src/package-managers/tester.test.js +33 -0
  147. package/src/package-managers/yarn/index.js +2 -0
  148. package/src/package-managers/yarn/scaffolder.js +19 -0
  149. package/src/package-managers/yarn/scaffolder.test.js +33 -0
  150. package/src/package-managers/yarn/tester.js +11 -0
  151. package/src/package-managers/yarn/tester.test.js +33 -0
  152. package/src/plugins-schemas.js +4 -0
  153. package/src/plugins-schemas.test.js +28 -0
  154. package/src/project-type/application/index.js +2 -0
  155. package/src/project-type/application/predicate.js +3 -0
  156. package/src/project-type/application/predicate.test.js +14 -0
  157. package/src/project-type/application/scaffolder.js +24 -0
  158. package/src/project-type/application/scaffolder.test.js +35 -0
  159. package/src/project-type/cli/index.js +3 -0
  160. package/src/project-type/cli/lifter.js +5 -0
  161. package/src/project-type/cli/lifter.test.js +20 -0
  162. package/src/project-type/cli/scaffolder.js +52 -0
  163. package/src/project-type/cli/scaffolder.test.js +103 -0
  164. package/src/project-type/cli/tester.js +3 -0
  165. package/src/project-type/cli/tester.test.js +14 -0
  166. package/src/project-type/index.js +3 -0
  167. package/src/project-type/lifter.js +23 -0
  168. package/src/project-type/lifter.test.js +69 -0
  169. package/src/project-type/monorepo/index.js +1 -0
  170. package/src/project-type/monorepo/scaffolder.js +16 -0
  171. package/src/project-type/monorepo/scaffolder.test.js +27 -0
  172. package/src/project-type/package/build-details.js +56 -0
  173. package/src/project-type/package/build-details.test.js +111 -0
  174. package/src/project-type/package/documentation.js +34 -0
  175. package/src/project-type/package/documentation.test.js +106 -0
  176. package/src/project-type/package/index.js +3 -0
  177. package/src/project-type/package/lifter.js +5 -0
  178. package/src/project-type/package/lifter.test.js +20 -0
  179. package/src/project-type/package/scaffolder.js +84 -0
  180. package/src/project-type/package/scaffolder.test.js +267 -0
  181. package/src/project-type/package/tester.js +5 -0
  182. package/src/project-type/package/tester.test.js +28 -0
  183. package/src/project-type/publishable/access-level.js +3 -0
  184. package/src/project-type/publishable/access-level.test.js +13 -0
  185. package/src/project-type/publishable/badges.js +20 -0
  186. package/src/project-type/publishable/badges.test.js +29 -0
  187. package/src/project-type/publishable/bundler/index.js +1 -0
  188. package/src/project-type/publishable/bundler/prompt.js +16 -0
  189. package/src/project-type/publishable/bundler/prompt.test.js +35 -0
  190. package/src/project-type/publishable/bundler/scaffolder.js +8 -0
  191. package/src/project-type/publishable/bundler/scaffolder.test.js +33 -0
  192. package/src/project-type/publishable/index.js +2 -0
  193. package/src/project-type/publishable/lifter.js +24 -0
  194. package/src/project-type/publishable/lifter.test.js +49 -0
  195. package/src/project-type/publishable/provenance/index.js +1 -0
  196. package/src/project-type/publishable/provenance/lifter.js +15 -0
  197. package/src/project-type/publishable/provenance/lifter.test.js +56 -0
  198. package/src/project-type/publishable/provenance/slsa.js +17 -0
  199. package/src/project-type/publishable/provenance/slsa.test.js +21 -0
  200. package/src/project-type/publishable/registry-resolver.js +15 -0
  201. package/src/project-type/publishable/registry-resolver.test.js +60 -0
  202. package/src/project-type/publishable/scaffolder.js +7 -0
  203. package/src/project-type/publishable/scaffolder.test.js +23 -0
  204. package/src/project-type/scaffolder.js +56 -0
  205. package/src/project-type/scaffolder.test.js +115 -0
  206. package/src/project-type/tester.js +9 -0
  207. package/src/project-type/tester.test.js +51 -0
  208. package/src/project-type-plugin/index.js +1 -0
  209. package/src/project-type-plugin/prompt.js +16 -0
  210. package/src/project-type-plugin/prompt.test.js +39 -0
  211. package/src/project-type-plugin/scaffolder.js +28 -0
  212. package/src/project-type-plugin/scaffolder.test.js +70 -0
  213. package/src/prompts/conditionals.js +39 -0
  214. package/src/prompts/conditionals.test.js +95 -0
  215. package/src/prompts/question-names.js +17 -0
  216. package/src/prompts/questions.js +158 -0
  217. package/src/prompts/questions.test.js +247 -0
  218. package/src/prompts/validators.js +9 -0
  219. package/src/prompts/validators.test.js +19 -0
  220. package/src/registries/index.js +2 -0
  221. package/src/registries/lifter.js +43 -0
  222. package/src/registries/lifter.test.js +63 -0
  223. package/src/registries/npm-config/list-builder.js +9 -0
  224. package/src/registries/npm-config/list-builder.test.js +43 -0
  225. package/src/registries/tester.js +3 -0
  226. package/src/registries/tester.test.js +9 -0
  227. package/src/runkit/badge/index.js +1 -0
  228. package/src/runkit/badge/scaffolder.js +13 -0
  229. package/src/runkit/badge/scaffolder.test.js +22 -0
  230. package/src/runkit/index.js +4 -0
  231. package/src/runkit/lifter.js +5 -0
  232. package/src/runkit/lifter.test.js +17 -0
  233. package/src/runkit/remover.js +3 -0
  234. package/src/runkit/remover.test.js +9 -0
  235. package/src/runkit/scaffolder.js +11 -0
  236. package/src/runkit/scaffolder.test.js +35 -0
  237. package/src/runkit/tester.js +3 -0
  238. package/src/runkit/tester.test.js +16 -0
  239. package/src/scaffolder.js +155 -0
  240. package/src/scaffolder.test.js +239 -0
  241. package/src/tester.js +17 -0
  242. package/src/tester.test.js +37 -0
  243. package/src/testing/index.js +1 -0
  244. package/src/testing/scaffolder.js +31 -0
  245. package/src/testing/scaffolder.test.js +63 -0
  246. package/src/testing/unit/index.js +1 -0
  247. package/src/testing/unit/prompt.js +15 -0
  248. package/src/testing/unit/prompt.test.js +33 -0
  249. package/src/testing/unit/scaffolder.js +30 -0
  250. package/src/testing/unit/scaffolder.test.js +54 -0
  251. package/src/vcs/ignore-lists-builder.js +6 -0
  252. package/src/vcs/ignore-lists-builder.test.js +25 -0
  253. package/src/vcs/schema.js +7 -0
  254. package/src/vcs/schema.test.js +40 -0
  255. package/src/verification/index.js +1 -0
  256. package/src/verification/scaffolder.js +35 -0
  257. package/src/verification/scaffolder.test.js +56 -0
@@ -0,0 +1,147 @@
1
+ import {validateOptions} from '@form8ion/core';
2
+
3
+ import {describe, expect, it} from 'vitest';
4
+ import any from '@travi/any';
5
+
6
+ import {
7
+ nameBasedConfigSchema,
8
+ projectNameSchema,
9
+ registriesSchema,
10
+ scopeBasedConfigSchema,
11
+ visibilitySchema
12
+ } from './schemas.js';
13
+
14
+ describe('options schemas', () => {
15
+ describe('scope-based configs', () => {
16
+ it('should require a provided config to be an object', () => {
17
+ expect(() => validateOptions(scopeBasedConfigSchema, any.word())).toThrowError('"value" must be of type object');
18
+ });
19
+
20
+ it('should require a provided config to have a `scope` property', () => {
21
+ expect(() => validateOptions(scopeBasedConfigSchema, {})).toThrowError('"scope" is required');
22
+ });
23
+
24
+ it('should require the `scope` to be a string', () => {
25
+ expect(() => validateOptions(scopeBasedConfigSchema, {scope: any.simpleObject()}))
26
+ .toThrowError('"scope" must be a string');
27
+ });
28
+
29
+ it('should require the `scope` to start with `@`', () => {
30
+ const scope = any.word();
31
+
32
+ expect(() => validateOptions(scopeBasedConfigSchema, {scope}))
33
+ .toThrowError(`"scope" with value "${scope}" fails to match the scope pattern`);
34
+ });
35
+
36
+ it('should not allow the `scope` to contain a `/`', () => {
37
+ const scope = `@${any.word()}/${any.word()}`;
38
+
39
+ expect(() => validateOptions(scopeBasedConfigSchema, {scope}))
40
+ .toThrowError(`"scope" with value "${scope}" fails to match the scope pattern`);
41
+ });
42
+
43
+ it('should allow `scope` to contain `-`', () => {
44
+ const scope = `@${any.word()}-${any.word()}`;
45
+
46
+ expect(validateOptions(scopeBasedConfigSchema, {scope})).toEqual({scope});
47
+ });
48
+ });
49
+
50
+ describe('name-based configs', () => {
51
+ it('should require a provided config to be an object', () => {
52
+ expect(() => validateOptions(nameBasedConfigSchema, any.word())).toThrowError('"value" must be of type object');
53
+ });
54
+
55
+ it('should require a provided config to have a `packageName` property', () => {
56
+ expect(() => validateOptions(nameBasedConfigSchema, {})).toThrowError('"packageName" is required');
57
+ });
58
+
59
+ it('should require the `packageName` to be a string', () => {
60
+ expect(() => validateOptions(nameBasedConfigSchema, {packageName: any.simpleObject()}))
61
+ .toThrowError('"packageName" must be a string');
62
+ });
63
+
64
+ it('should require a provided config to have a `name` property', () => {
65
+ expect(() => validateOptions(nameBasedConfigSchema, {packageName: any.word()}))
66
+ .toThrowError('"name" is required');
67
+ });
68
+
69
+ it('should require the `name` to be a string', () => {
70
+ expect(() => validateOptions(nameBasedConfigSchema, {packageName: any.word(), name: any.simpleObject()}))
71
+ .toThrowError('"name" must be a string');
72
+ });
73
+ });
74
+
75
+ describe('registries', () => {
76
+ const key = any.word();
77
+
78
+ it('should return an empty object when no registries are provided', () => {
79
+ expect(validateOptions(registriesSchema)).toEqual({});
80
+ });
81
+
82
+ it('should return the validated registries when valid', () => {
83
+ const registries = {[any.word()]: any.url()};
84
+
85
+ expect(validateOptions(registriesSchema, registries)).toEqual(registries);
86
+ });
87
+
88
+ it('should require the `registries` definition to be an object', () => {
89
+ expect(() => validateOptions(registriesSchema, any.word())).toThrowError('"value" must be of type object');
90
+ });
91
+
92
+ it('should require the values to be strings', () => {
93
+ expect(() => validateOptions(registriesSchema, {[key]: any.integer()})).toThrowError(`"${key}" must be a string`);
94
+ });
95
+
96
+ it('should require the values to be URIs', () => {
97
+ expect(() => validateOptions(registriesSchema, {[key]: any.string()}))
98
+ .toThrowError(`"${key}" must be a valid uri`);
99
+ });
100
+ });
101
+
102
+ describe('visibility', () => {
103
+ it('should require a value to be provided', () => {
104
+ expect(() => validateOptions(visibilitySchema)).toThrowError('"value" is required');
105
+ });
106
+
107
+ it('should allow `Public` as a valid value', () => {
108
+ const visibility = 'Public';
109
+
110
+ expect(validateOptions(visibilitySchema, visibility)).toEqual(visibility);
111
+ });
112
+
113
+ it('should allow `Private` as a valid value', () => {
114
+ const visibility = 'Private';
115
+
116
+ expect(validateOptions(visibilitySchema, visibility)).toEqual(visibility);
117
+ });
118
+
119
+ it('should consider values other than `Public` and `Private` as invalid', () => {
120
+ expect(() => validateOptions(visibilitySchema, any.word()))
121
+ .toThrowError('"value" must be one of [Public, Private]');
122
+ });
123
+ });
124
+
125
+ describe('project name', () => {
126
+ it('should return the validated name', () => {
127
+ const projectName = any.word();
128
+
129
+ expect(validateOptions(projectNameSchema, projectName)).toEqual(projectName);
130
+ });
131
+
132
+ it('should require a value to be provided', () => {
133
+ expect(() => validateOptions(projectNameSchema)).toThrowError('"value" is required');
134
+ });
135
+
136
+ it('should require a value to be a string', () => {
137
+ expect(() => validateOptions(projectNameSchema, any.simpleObject())).toThrowError('"value" must be a string');
138
+ });
139
+
140
+ it('should prevent the project name from including a scope', () => {
141
+ const projectName = `@${any.word()}/${any.word()}`;
142
+
143
+ expect(() => validateOptions(projectNameSchema, projectName))
144
+ .toThrowError(`"value" with value "${projectName}" matches the inverted pattern: /^@\\w*\\//`);
145
+ });
146
+ });
147
+ });
@@ -0,0 +1,45 @@
1
+ import joi from 'joi';
2
+ import {validateOptions} from '@form8ion/core';
3
+
4
+ import {pluginsSchema} from '../plugins-schemas.js';
5
+ import {
6
+ nameBasedConfigSchema,
7
+ projectNameSchema,
8
+ registriesSchema,
9
+ scopeBasedConfigSchema,
10
+ visibilitySchema
11
+ } from './schemas.js';
12
+ import {vcsSchema} from '../vcs/schema.js';
13
+
14
+ export function validate(options) {
15
+ const schema = joi.object({
16
+ projectRoot: joi.string().required(),
17
+ projectName: projectNameSchema,
18
+ visibility: visibilitySchema,
19
+ license: joi.string().required(),
20
+ description: joi.string(),
21
+ pathWithinParent: joi.string(),
22
+ decisions: joi.object(),
23
+ vcs: vcsSchema,
24
+ configs: joi.object({
25
+ eslint: scopeBasedConfigSchema,
26
+ typescript: scopeBasedConfigSchema,
27
+ prettier: scopeBasedConfigSchema,
28
+ commitlint: nameBasedConfigSchema,
29
+ babelPreset: nameBasedConfigSchema,
30
+ remark: joi.string(),
31
+ registries: registriesSchema
32
+ }).default({registries: {}}),
33
+ plugins: {
34
+ unitTestFrameworks: pluginsSchema,
35
+ packageBundlers: pluginsSchema,
36
+ applicationTypes: pluginsSchema,
37
+ packageTypes: pluginsSchema,
38
+ monorepoTypes: pluginsSchema,
39
+ hosts: pluginsSchema,
40
+ ciServices: pluginsSchema
41
+ }
42
+ }).required();
43
+
44
+ return validateOptions(schema, options);
45
+ }
@@ -0,0 +1,79 @@
1
+ import joi from 'joi';
2
+ import {validateOptions} from '@form8ion/core';
3
+
4
+ import {beforeEach, describe, expect, it, vi} from 'vitest';
5
+ import {when} from 'vitest-when';
6
+ import any from '@travi/any';
7
+
8
+ import {
9
+ nameBasedConfigSchema, projectNameSchema,
10
+ registriesSchema,
11
+ scopeBasedConfigSchema,
12
+ visibilitySchema
13
+ } from './schemas.js';
14
+ import {pluginsSchema} from '../plugins-schemas.js';
15
+ import {validate} from './validator.js';
16
+ import {vcsSchema} from '../vcs/schema.js';
17
+
18
+ vi.mock('@form8ion/core');
19
+
20
+ describe('options validator', () => {
21
+ beforeEach(() => {
22
+ vi.spyOn(joi, 'object');
23
+ vi.spyOn(joi, 'string');
24
+ });
25
+
26
+ it('should validate the provided options against the js-plugin schema', () => {
27
+ const options = any.simpleObject();
28
+ const validatedOptions = any.simpleObject();
29
+ const schema = any.simpleObject();
30
+ const joiRequiredObject = vi.fn();
31
+ const joiRequiredString = vi.fn();
32
+ const requiredString = any.simpleObject();
33
+ const joiString = {required: joiRequiredString};
34
+ const joiObject = any.simpleObject();
35
+ const configsDefault = vi.fn();
36
+ const configs = any.simpleObject();
37
+ when(joi.string).calledWith().thenReturn(joiString);
38
+ when(joiRequiredString).calledWith().thenReturn(requiredString);
39
+ when(joi.object).calledWith().thenReturn(joiObject);
40
+ when(joi.object)
41
+ .calledWith({
42
+ eslint: scopeBasedConfigSchema,
43
+ typescript: scopeBasedConfigSchema,
44
+ prettier: scopeBasedConfigSchema,
45
+ commitlint: nameBasedConfigSchema,
46
+ babelPreset: nameBasedConfigSchema,
47
+ remark: joiString,
48
+ registries: registriesSchema
49
+ })
50
+ .thenReturn({default: configsDefault});
51
+ when(configsDefault).calledWith({registries: {}}).thenReturn(configs);
52
+ when(joi.object)
53
+ .calledWith({
54
+ projectRoot: requiredString,
55
+ projectName: projectNameSchema,
56
+ license: requiredString,
57
+ visibility: visibilitySchema,
58
+ description: joiString,
59
+ pathWithinParent: joiString,
60
+ decisions: joiObject,
61
+ vcs: vcsSchema,
62
+ configs,
63
+ plugins: {
64
+ unitTestFrameworks: pluginsSchema,
65
+ packageBundlers: pluginsSchema,
66
+ applicationTypes: pluginsSchema,
67
+ packageTypes: pluginsSchema,
68
+ monorepoTypes: pluginsSchema,
69
+ hosts: pluginsSchema,
70
+ ciServices: pluginsSchema
71
+ }
72
+ })
73
+ .thenReturn({required: joiRequiredObject});
74
+ when(joiRequiredObject).calledWith().thenReturn(schema);
75
+ when(validateOptions).calledWith(schema, options).thenReturn(validatedOptions);
76
+
77
+ expect(validate(options)).toEqual(validatedOptions);
78
+ });
79
+ });
@@ -0,0 +1,18 @@
1
+ import {dialects} from '@form8ion/javascript-core';
2
+
3
+ export default function scaffoldPackageJsonDetails({
4
+ packageName,
5
+ dialect,
6
+ license,
7
+ author,
8
+ description
9
+ }) {
10
+ return {
11
+ name: packageName,
12
+ description,
13
+ license,
14
+ type: dialects.ESM === dialect ? 'module' : 'commonjs',
15
+ author: `${author.name}${author.email ? ` <${author.email}>` : ''}${author.url ? ` (${author.url})` : ''}`,
16
+ scripts: {}
17
+ };
18
+ }
@@ -0,0 +1,51 @@
1
+ import {dialects} from '@form8ion/javascript-core';
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+ import any from '@travi/any';
5
+
6
+ import defineDetails from './details.js';
7
+
8
+ describe('package details builder', () => {
9
+ const authorName = `${any.word()} ${any.word()}`;
10
+ const authorEmail = any.email();
11
+ const authorWebsite = any.url();
12
+
13
+ it('should define the initial details for the package.json file', () => {
14
+ const packageName = any.word();
15
+ const license = any.word();
16
+ const description = any.sentence();
17
+
18
+ expect(defineDetails({packageName, description, license, author: {name: authorName}})).toEqual({
19
+ name: packageName,
20
+ description,
21
+ license,
22
+ type: 'commonjs',
23
+ author: authorName,
24
+ scripts: {}
25
+ });
26
+ });
27
+
28
+ it('should define `type` as `module` when the dialect is esm', () => {
29
+ const {type} = defineDetails({author: {}, dialect: dialects.ESM});
30
+
31
+ expect(type).toEqual('module');
32
+ });
33
+
34
+ it('should include author email when defined', () => {
35
+ const {author} = defineDetails({author: {name: authorName, email: authorEmail}});
36
+
37
+ expect(author).toEqual(`${authorName} <${authorEmail}>`);
38
+ });
39
+
40
+ it('should include author website when defined', () => {
41
+ const {author} = defineDetails({author: {name: authorName, url: authorWebsite}});
42
+
43
+ expect(author).toEqual(`${authorName} (${authorWebsite})`);
44
+ });
45
+
46
+ it('should include author email and website when both are defined', () => {
47
+ const {author} = defineDetails({author: {name: authorName, email: authorEmail, url: authorWebsite}});
48
+
49
+ expect(author).toEqual(`${authorName} <${authorEmail}> (${authorWebsite})`);
50
+ });
51
+ });
@@ -0,0 +1,2 @@
1
+ export {default as scaffold} from './scaffolder.js';
2
+ export {default as lift} from './lifter.js';
@@ -0,0 +1,47 @@
1
+ import {promises as fs} from 'node:fs';
2
+ import deepmerge from 'deepmerge';
3
+ import {info} from '@travi/cli-messages';
4
+ import {writePackageJson} from '@form8ion/javascript-core';
5
+
6
+ import sortPackageProperties from './property-sorter.js';
7
+ import defineVcsHostDetails from './vcs-host-details.js';
8
+ import {process as processDependencies} from '../dependencies/index.js';
9
+ import {lift as liftScripts} from './scripts/index.js';
10
+
11
+ export default async function liftPackageJson({
12
+ projectRoot,
13
+ scripts,
14
+ tags,
15
+ dependencies,
16
+ devDependencies,
17
+ packageManager,
18
+ vcs,
19
+ pathWithinParent
20
+ }) {
21
+ info('Updating `package.json`', {level: 'secondary'});
22
+
23
+ const existingPackageJsonContents = JSON.parse(await fs.readFile(`${projectRoot}/package.json`, 'utf-8'));
24
+ const {scripts: liftedScripts, dependencies: scriptDependencies} = liftScripts({
25
+ existingScripts: existingPackageJsonContents.scripts,
26
+ scripts
27
+ });
28
+
29
+ await writePackageJson({
30
+ projectRoot,
31
+ config: sortPackageProperties({
32
+ ...existingPackageJsonContents,
33
+ ...defineVcsHostDetails(vcs, pathWithinParent),
34
+ scripts: liftedScripts,
35
+ ...tags && {
36
+ keywords: existingPackageJsonContents.keywords ? [...existingPackageJsonContents.keywords, ...tags] : tags
37
+ }
38
+ })
39
+ });
40
+
41
+ await processDependencies({
42
+ dependencies: deepmerge(dependencies, scriptDependencies),
43
+ devDependencies,
44
+ projectRoot,
45
+ packageManager
46
+ });
47
+ }
@@ -0,0 +1,100 @@
1
+ import {promises as fs} from 'node:fs';
2
+ import deepmerge from 'deepmerge';
3
+ import {writePackageJson} from '@form8ion/javascript-core';
4
+
5
+ import {describe, it, expect, vi, beforeEach} from 'vitest';
6
+ import any from '@travi/any';
7
+ import {when} from 'vitest-when';
8
+
9
+ import {process as processDependencies} from '../dependencies/index.js';
10
+ import {lift as liftScripts} from './scripts/index.js';
11
+ import defineVcsHostDetails from './vcs-host-details.js';
12
+ import sortPackageProperties from './property-sorter.js';
13
+ import liftPackage from './lifter.js';
14
+
15
+ vi.mock('node:fs');
16
+ vi.mock('deepmerge');
17
+ vi.mock('@form8ion/javascript-core');
18
+ vi.mock('../dependencies/index.js');
19
+ vi.mock('./scripts/index.js');
20
+ vi.mock('./vcs-host-details.js');
21
+ vi.mock('./property-sorter.js');
22
+
23
+ describe('package.json lifter', () => {
24
+ const projectRoot = any.string();
25
+ const dependencies = any.simpleObject();
26
+ const mergedDependencies = any.simpleObject();
27
+ const packageManager = any.word();
28
+ const vcs = any.simpleObject();
29
+ const pathWithinParent = any.string();
30
+ const scripts = any.simpleObject();
31
+ const existingScripts = any.simpleObject();
32
+ const liftedScripts = any.simpleObject();
33
+ const vcsDetails = any.simpleObject();
34
+ const config = any.simpleObject();
35
+ const tags = any.listOf(any.word);
36
+ const scriptDependencies = any.simpleObject();
37
+
38
+ beforeEach(() => {
39
+ when(defineVcsHostDetails).calledWith(vcs, pathWithinParent).thenReturn(vcsDetails);
40
+ when(liftScripts)
41
+ .calledWith({existingScripts, scripts})
42
+ .thenReturn({scripts: liftedScripts, dependencies: scriptDependencies});
43
+ when(deepmerge).calledWith(dependencies, scriptDependencies).thenReturn(mergedDependencies);
44
+ });
45
+
46
+ it('should update package.json properties and process dependencies', async () => {
47
+ const existingPackageContents = {...any.simpleObject(), scripts: existingScripts};
48
+ const devDependencies = any.listOf(any.word);
49
+ when(fs.readFile)
50
+ .calledWith(`${projectRoot}/package.json`, 'utf-8')
51
+ .thenResolve(JSON.stringify(existingPackageContents));
52
+ when(sortPackageProperties)
53
+ .calledWith({...existingPackageContents, ...vcsDetails, scripts: liftedScripts})
54
+ .thenReturn(config);
55
+
56
+ await liftPackage({dependencies, devDependencies, projectRoot, packageManager, vcs, pathWithinParent, scripts});
57
+
58
+ expect(writePackageJson).toHaveBeenCalledWith({projectRoot, config});
59
+ expect(processDependencies).toHaveBeenCalledWith({
60
+ dependencies: mergedDependencies,
61
+ devDependencies,
62
+ projectRoot,
63
+ packageManager
64
+ });
65
+ });
66
+
67
+ it('should update keywords if tags are provided', async () => {
68
+ const existingPackageContents = {...any.simpleObject(), scripts: existingScripts};
69
+ when(fs.readFile)
70
+ .calledWith(`${projectRoot}/package.json`, 'utf-8')
71
+ .thenResolve(JSON.stringify(existingPackageContents));
72
+ when(sortPackageProperties)
73
+ .calledWith({...existingPackageContents, ...vcsDetails, scripts: liftedScripts, keywords: tags})
74
+ .thenReturn(config);
75
+
76
+ await liftPackage({dependencies, projectRoot, packageManager, vcs, pathWithinParent, scripts, tags});
77
+
78
+ expect(writePackageJson).toHaveBeenCalledWith({projectRoot, config});
79
+ });
80
+
81
+ it('should append the provided tags to existing keywords', async () => {
82
+ const existingKeywords = any.listOf(any.word);
83
+ const existingPackageContents = {...any.simpleObject(), scripts: existingScripts, keywords: existingKeywords};
84
+ when(fs.readFile)
85
+ .calledWith(`${projectRoot}/package.json`, 'utf-8')
86
+ .thenResolve(JSON.stringify(existingPackageContents));
87
+ when(sortPackageProperties)
88
+ .calledWith({
89
+ ...existingPackageContents,
90
+ ...vcsDetails,
91
+ scripts: liftedScripts,
92
+ keywords: [...existingKeywords, ...tags]
93
+ })
94
+ .thenReturn(config);
95
+
96
+ await liftPackage({dependencies, projectRoot, packageManager, vcs, pathWithinParent, scripts, tags});
97
+
98
+ expect(writePackageJson).toHaveBeenCalledWith({projectRoot, config});
99
+ });
100
+ });
@@ -0,0 +1,13 @@
1
+ import {EOL} from 'os';
2
+ import validatePackageName from '../../thirdparty-wrappers/validate-npm-package-name.js';
3
+
4
+ export default function determinePackageName(projectName, scope) {
5
+ const name = `${scope ? `@${scope}/` : ''}${projectName}`;
6
+
7
+ const {validForNewPackages, errors} = validatePackageName(name);
8
+
9
+ if (validForNewPackages) return name;
10
+ if (1 === errors.length && errors.includes('name cannot start with a period')) return projectName.slice(1);
11
+
12
+ throw new Error(`The package name ${name} is invalid:${EOL}\t* ${errors.join(`${EOL}\t* `)}`);
13
+ }
@@ -0,0 +1,52 @@
1
+ import {EOL} from 'os';
2
+
3
+ import any from '@travi/any';
4
+ import {describe, it, expect, vi} from 'vitest';
5
+ import {when} from 'vitest-when';
6
+
7
+ import validatePackageName from '../../thirdparty-wrappers/validate-npm-package-name.js';
8
+ import packageName from './package-name.js';
9
+
10
+ vi.mock('../../thirdparty-wrappers/validate-npm-package-name.js');
11
+
12
+ describe('package name', () => {
13
+ const projectName = any.word();
14
+
15
+ it('should return the project name if no scope is provided', () => {
16
+ when(validatePackageName).calledWith(projectName).thenReturn({validForNewPackages: true});
17
+
18
+ expect(packageName(projectName)).toEqual(projectName);
19
+ });
20
+
21
+ it('should include the scope in the name when provided', () => {
22
+ const scope = any.word();
23
+ const scopedName = `@${scope}/${projectName}`;
24
+ when(validatePackageName).calledWith(scopedName).thenReturn({validForNewPackages: true});
25
+
26
+ expect(packageName(projectName, scope)).toEqual(scopedName);
27
+ });
28
+
29
+ it('should throw and error when the value is not valid for npm', () => {
30
+ const errors = any.listOf(any.sentence);
31
+ when(validatePackageName).calledWith(projectName).thenReturn({validForNewPackages: false, errors});
32
+
33
+ expect(() => packageName(projectName))
34
+ .toThrowError(`The package name ${projectName} is invalid:${EOL}\t* ${errors.join(`${EOL}\t* `)}`);
35
+ });
36
+
37
+ it('should strip a leading dot from the package name', () => {
38
+ when(validatePackageName)
39
+ .calledWith(`.${projectName}`)
40
+ .thenReturn({validForNewPackages: false, errors: ['name cannot start with a period']});
41
+
42
+ expect(packageName(`.${projectName}`)).toEqual(projectName);
43
+ });
44
+
45
+ it('should throw an error if more validation erros than a leading dot exist', () => {
46
+ const errors = [...any.listOf(any.sentence), 'name cannot start with a period'];
47
+ when(validatePackageName).calledWith(projectName).thenReturn({validForNewPackages: false, errors});
48
+
49
+ expect(() => packageName(projectName))
50
+ .toThrowError(`The package name ${projectName} is invalid:${EOL}\t* ${errors.join(`${EOL}\t* `)}`);
51
+ });
52
+ });
@@ -0,0 +1,38 @@
1
+ import sortObjectKeys from 'sort-object-keys';
2
+
3
+ export default function sortProperties(packageContents) {
4
+ return sortObjectKeys(
5
+ packageContents,
6
+ [
7
+ 'name',
8
+ 'description',
9
+ 'license',
10
+ 'version',
11
+ 'private',
12
+ 'type',
13
+ 'engines',
14
+ 'author',
15
+ 'contributors',
16
+ 'repository',
17
+ 'bugs',
18
+ 'homepage',
19
+ 'funding',
20
+ 'keywords',
21
+ 'runkitExampleFilename',
22
+ 'exports',
23
+ 'bin',
24
+ 'main',
25
+ 'module',
26
+ 'types',
27
+ 'sideEffects',
28
+ 'scripts',
29
+ 'files',
30
+ 'publishConfig',
31
+ 'packageManager',
32
+ 'config',
33
+ 'dependencies',
34
+ 'devDependencies',
35
+ 'peerDependencies'
36
+ ]
37
+ );
38
+ }
@@ -0,0 +1,56 @@
1
+ import sortObjectKeys from 'sort-object-keys';
2
+
3
+ import {afterEach, describe, expect, it, vi} from 'vitest';
4
+ import any from '@travi/any';
5
+ import {when} from 'vitest-when';
6
+
7
+ import sortProperties from './property-sorter.js';
8
+
9
+ vi.mock('sort-object-keys');
10
+
11
+ describe('package.json property sorter', () => {
12
+ afterEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ it('should sort the package properties based on the defined order', () => {
17
+ const packageContents = any.simpleObject();
18
+ const sortedPackageContents = any.simpleObject();
19
+ when(sortObjectKeys).calledWith(
20
+ packageContents,
21
+ [
22
+ 'name',
23
+ 'description',
24
+ 'license',
25
+ 'version',
26
+ 'private',
27
+ 'type',
28
+ 'engines',
29
+ 'author',
30
+ 'contributors',
31
+ 'repository',
32
+ 'bugs',
33
+ 'homepage',
34
+ 'funding',
35
+ 'keywords',
36
+ 'runkitExampleFilename',
37
+ 'exports',
38
+ 'bin',
39
+ 'main',
40
+ 'module',
41
+ 'types',
42
+ 'sideEffects',
43
+ 'scripts',
44
+ 'files',
45
+ 'publishConfig',
46
+ 'packageManager',
47
+ 'config',
48
+ 'dependencies',
49
+ 'devDependencies',
50
+ 'peerDependencies'
51
+ ]
52
+ ).thenReturn(sortedPackageContents);
53
+
54
+ expect(sortProperties(packageContents)).toEqual(sortedPackageContents);
55
+ });
56
+ });