@schalkneethling/miyagi-core 4.3.1 → 4.4.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.
package/lib/cli/run.js ADDED
@@ -0,0 +1,281 @@
1
+ import { t } from "../i18n/index.js";
2
+ import initRendering from "../init/rendering.js";
3
+ import log from "../logger.js";
4
+ import mockGenerator from "../generator/mocks.js";
5
+ import getConfig from "../config.js";
6
+ import createCli from "../init/args.js";
7
+ import {
8
+ lint,
9
+ component as createComponentViaCli,
10
+ drupalAssets,
11
+ doctor,
12
+ } from "./index.js";
13
+ import { EXIT_CODES, MiyagiError } from "../errors.js";
14
+
15
+ /**
16
+ * @returns {object}
17
+ */
18
+ function createSuccessResult() {
19
+ return {
20
+ success: true,
21
+ code: EXIT_CODES.SUCCESS,
22
+ shouldExit: true,
23
+ };
24
+ }
25
+
26
+ /**
27
+ * @param {string} message
28
+ * @param {number} [code]
29
+ * @returns {object}
30
+ */
31
+ function createFailureResult(message, code = EXIT_CODES.GENERAL_ERROR) {
32
+ log("error", message);
33
+ return {
34
+ success: false,
35
+ code,
36
+ shouldExit: true,
37
+ message,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * @param {number} code
43
+ * @param {string[]} argv
44
+ * @returns {void}
45
+ */
46
+ function maybeLogDoctorHint(code, argv) {
47
+ if (code !== EXIT_CODES.CONFIG_ERROR) {
48
+ return;
49
+ }
50
+
51
+ if (argv.includes("doctor")) {
52
+ return;
53
+ }
54
+
55
+ log("info", "Run `miyagi doctor` for a setup check.");
56
+ }
57
+
58
+ /**
59
+ * @param {object} args
60
+ * @param {boolean} [isBuild]
61
+ * @param {boolean} [isComponentGenerator]
62
+ * @returns {Promise<object>}
63
+ */
64
+ async function loadCliConfig(args, isBuild = false, isComponentGenerator = false) {
65
+ global.config = await getConfig(args, isBuild, isComponentGenerator);
66
+
67
+ if (!global.config.components.folder && !global.config.docs.folder) {
68
+ return createFailureResult(
69
+ "Please specify at least either components.folder or docs.folder in your configuration file.",
70
+ EXIT_CODES.CONFIG_ERROR,
71
+ );
72
+ }
73
+
74
+ return {
75
+ success: true,
76
+ config: global.config,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * @param {object} args
82
+ * @returns {void}
83
+ */
84
+ function applyCliEnv(args) {
85
+ if (args.verbose) {
86
+ process.env.VERBOSE = String(args.verbose);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @param {object} args
92
+ * @param {object} [options]
93
+ * @param {string} [options.defaultNodeEnv]
94
+ * @param {string} [options.forcedNodeEnv]
95
+ * @param {boolean} [options.isBuild]
96
+ * @param {boolean} [options.isComponentGenerator]
97
+ * @param {Function} run
98
+ * @returns {Promise<object>}
99
+ */
100
+ async function withCliConfig(args, options = {}, run) {
101
+ applyCliEnv(args);
102
+
103
+ if (options.forcedNodeEnv) {
104
+ process.env.NODE_ENV = options.forcedNodeEnv;
105
+ } else if (options.defaultNodeEnv && !process.env.NODE_ENV) {
106
+ process.env.NODE_ENV = options.defaultNodeEnv;
107
+ }
108
+
109
+ const configResult = await loadCliConfig(
110
+ args,
111
+ options.isBuild,
112
+ options.isComponentGenerator,
113
+ );
114
+ if (!configResult.success) {
115
+ return configResult;
116
+ }
117
+
118
+ return await run(configResult.config);
119
+ }
120
+
121
+ /**
122
+ * @param {object} args
123
+ * @returns {Promise<object>}
124
+ */
125
+ async function runStartCommand(args) {
126
+ return await withCliConfig(
127
+ args,
128
+ { defaultNodeEnv: "development" },
129
+ async (config) => {
130
+ log(
131
+ "info",
132
+ t("serverStarting").replace("{{node_env}}", process.env.NODE_ENV),
133
+ );
134
+
135
+ return await initRendering(config);
136
+ },
137
+ );
138
+ }
139
+
140
+ /**
141
+ * @param {object} args
142
+ * @returns {Promise<object>}
143
+ */
144
+ async function runBuildCommand(args) {
145
+ return await withCliConfig(
146
+ args,
147
+ { forcedNodeEnv: "production", isBuild: true },
148
+ async (config) => {
149
+ log("info", t("buildStarting"));
150
+ return await initRendering(config);
151
+ },
152
+ );
153
+ }
154
+
155
+ /**
156
+ * @param {object} args
157
+ * @returns {Promise<object>}
158
+ */
159
+ async function runComponentCommand(args) {
160
+ return await withCliConfig(
161
+ args,
162
+ { defaultNodeEnv: "development", isComponentGenerator: true },
163
+ async () => {
164
+ log("info", t("generator.starting"));
165
+ return await createComponentViaCli(args);
166
+ },
167
+ );
168
+ }
169
+
170
+ /**
171
+ * @param {object} args
172
+ * @returns {Promise<object>}
173
+ */
174
+ async function runMocksCommand(args) {
175
+ return await withCliConfig(
176
+ args,
177
+ { defaultNodeEnv: "development" },
178
+ async (config) => {
179
+ const result = await mockGenerator(args.component, config.files);
180
+ if (result?.message) {
181
+ log(result.message.type, result.message.text, result.message.verbose);
182
+ }
183
+
184
+ return {
185
+ success: result.success,
186
+ code: result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERAL_ERROR,
187
+ shouldExit: true,
188
+ message: result.message?.text,
189
+ };
190
+ },
191
+ );
192
+ }
193
+
194
+ /**
195
+ * @param {object} args
196
+ * @returns {Promise<object>}
197
+ */
198
+ async function runLintCommand(args) {
199
+ applyCliEnv(args);
200
+ return await lint(args);
201
+ }
202
+
203
+ /**
204
+ * @param {object} args
205
+ * @returns {Promise<object>}
206
+ */
207
+ async function runDrupalAssetsCommand(args) {
208
+ return await withCliConfig(
209
+ args,
210
+ { forcedNodeEnv: "development" },
211
+ async () => await drupalAssets(args),
212
+ );
213
+ }
214
+
215
+ /**
216
+ * @param {object} args
217
+ * @returns {Promise<object>}
218
+ */
219
+ async function runDoctorCommand(args) {
220
+ applyCliEnv(args);
221
+ return await doctor(args);
222
+ }
223
+
224
+ /**
225
+ * @param {Error|MiyagiError|string} error
226
+ * @param {string[]} argv
227
+ * @returns {object}
228
+ */
229
+ function normalizeCliError(error, argv) {
230
+ if (error instanceof MiyagiError) {
231
+ if (!error.logged) {
232
+ log("error", error.message);
233
+ }
234
+ maybeLogDoctorHint(error.code, argv);
235
+
236
+ return {
237
+ success: false,
238
+ code: error.code,
239
+ shouldExit: true,
240
+ message: error.message,
241
+ };
242
+ }
243
+
244
+ const message = error instanceof Error ? error.message : String(error);
245
+ log("error", message);
246
+ maybeLogDoctorHint(EXIT_CODES.GENERAL_ERROR, argv);
247
+ return {
248
+ success: false,
249
+ code: EXIT_CODES.GENERAL_ERROR,
250
+ shouldExit: true,
251
+ message,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * @param {string[]} [argv]
257
+ * @returns {Promise<object>}
258
+ */
259
+ export async function runCli(argv = process.argv) {
260
+ const { cli, getResult } = createCli(
261
+ {
262
+ start: runStartCommand,
263
+ build: runBuildCommand,
264
+ new: runComponentCommand,
265
+ mocks: runMocksCommand,
266
+ lint: runLintCommand,
267
+ drupalAssets: runDrupalAssetsCommand,
268
+ doctor: runDoctorCommand,
269
+ },
270
+ argv,
271
+ );
272
+
273
+ try {
274
+ await cli.parseAsync();
275
+ const result = getResult() || createSuccessResult();
276
+ maybeLogDoctorHint(result.code, argv);
277
+ return result;
278
+ } catch (error) {
279
+ return normalizeCliError(error, argv);
280
+ }
281
+ }
package/lib/config.js CHANGED
@@ -62,6 +62,16 @@ function getCliArgs(args) {
62
62
 
63
63
  delete cliArgs._;
64
64
  delete cliArgs.$0;
65
+ delete cliArgs.component;
66
+ delete cliArgs.verbose;
67
+ delete cliArgs.skip;
68
+ delete cliArgs.only;
69
+ delete cliArgs.engine;
70
+ delete cliArgs.config;
71
+ delete cliArgs.libraries;
72
+ delete cliArgs.components;
73
+ delete cliArgs.ignorePrefixes;
74
+ delete cliArgs.dryRun;
65
75
 
66
76
  if (cliArgs.folder) {
67
77
  buildArgs.folder = cliArgs.folder;
@@ -1,173 +1,174 @@
1
1
  import AJV from "ajv";
2
2
 
3
3
  export default {
4
- defaultUserConfig: {
5
- assets: {
6
- root: "",
7
- css: [],
8
- shared: {
9
- css: [],
10
- js: [],
11
- },
12
- isolateComponents: false,
13
- customProperties: {
14
- files: [],
15
- prefixes: {
16
- typo: "typo",
17
- color: "color",
18
- spacing: "spacing",
19
- },
20
- },
21
- folder: [],
22
- js: [],
23
- manifest: null,
24
- },
25
- build: {
26
- basePath: "/",
27
- folder: "build",
28
- },
29
- docs: {
30
- folder: "docs",
31
- },
32
- components: {
33
- folder: "src",
34
- ignores: [
35
- "node_modules",
36
- ".git",
37
- "package.json",
38
- "package-lock.json",
39
- ".miyagi.js",
40
- ".miyagi.mjs",
41
- ],
42
- lang: "en",
43
- textDirection: "ltr",
44
- },
45
- engine: {
46
- render: null,
47
- options: {},
48
- },
49
- lint: {
50
- logLevel: "error",
51
- },
52
- extensions: [],
53
- files: {
54
- css: {
55
- abbr: "css",
56
- name: "index",
57
- extension: "css",
58
- },
59
- js: {
60
- abbr: "js",
61
- name: "index",
62
- extension: "js",
63
- },
64
- mocks: {
65
- abbr: "mocks",
66
- name: "mocks",
67
- extension: ["json", "js"],
68
- },
69
- schema: {
70
- abbr: "schema",
71
- name: "schema",
72
- extension: "json",
73
- },
74
- templates: {
75
- abbr: "tpl",
76
- name: "index",
77
- },
78
- },
79
- namespaces: {},
80
- projectName: "miyagi",
81
- ui: {
82
- mode: "light",
83
- lang: "en",
84
- reload: true,
85
- reloadAfterChanges: {
86
- componentAssets: false,
87
- },
88
- textDirection: "ltr",
89
- theme: {
90
- css: null,
91
- favicon: null,
92
- js: null,
93
- logo: {
94
- light: null,
95
- dark: null,
96
- },
97
- },
98
- watchConfigFile: true,
99
- },
100
- watch: {
101
- enabled: true,
102
- backend: "chokidar",
103
- sources: [],
104
- ignore: {
105
- defaults: true,
106
- patterns: [],
107
- },
108
- behavior: {
109
- debounceMs: 60,
110
- coalesceWindowMs: 120,
111
- awaitWriteFinish: {
112
- enabled: true,
113
- stabilityThresholdMs: 200,
114
- pollIntervalMs: 50,
115
- },
116
- },
117
- reload: {
118
- enabled: true,
119
- rules: {
120
- template: "iframe",
121
- data: "parent",
122
- docs: "parent",
123
- schema: "iframe",
124
- componentAsset: "none",
125
- globalCss: "none",
126
- globalJs: "none",
127
- unknown: "parent",
128
- },
129
- },
130
- socket: {
131
- reconnect: {
132
- enabled: true,
133
- initialDelayMs: 250,
134
- maxDelayMs: 5000,
135
- jitter: true,
136
- },
137
- heartbeat: {
138
- enabled: true,
139
- intervalMs: 30000,
140
- },
141
- },
142
- report: {
143
- enabled: true,
144
- onStart: true,
145
- format: "summary",
146
- destination: "stdout",
147
- useColors: true,
148
- },
149
- configFile: {
150
- enabled: true,
151
- },
152
- debug: {
153
- logEvents: false,
154
- logDecisions: false,
155
- logResolvedSources: false,
156
- },
157
- },
158
- schema: {
159
- ajv: AJV,
160
- verbose: false,
161
- },
162
- schemaValidationMode: "collect-all",
163
- },
164
- projectName: "miyagi",
165
- defaultPort: 5000,
166
- folders: {
167
- assets: {
168
- development: "frontend/assets",
169
- production: "dist",
170
- },
171
- },
172
- defaultVariationName: "default",
4
+ defaultUserConfig: {
5
+ assets: {
6
+ root: "",
7
+ css: [],
8
+ shared: {
9
+ css: [],
10
+ js: [],
11
+ },
12
+ isolateComponents: false,
13
+ customProperties: {
14
+ files: [],
15
+ prefixes: {
16
+ typo: "typo",
17
+ color: "color",
18
+ spacing: "spacing",
19
+ },
20
+ },
21
+ folder: [],
22
+ js: [],
23
+ manifest: null,
24
+ },
25
+ build: {
26
+ basePath: "/",
27
+ folder: "build",
28
+ },
29
+ docs: {
30
+ folder: "docs",
31
+ },
32
+ components: {
33
+ folder: "src",
34
+ ignores: [
35
+ "node_modules",
36
+ ".git",
37
+ "package.json",
38
+ "package-lock.json",
39
+ ".miyagi.js",
40
+ ".miyagi.mjs",
41
+ ],
42
+ lang: "en",
43
+ textDirection: "ltr",
44
+ },
45
+ engine: {
46
+ render: null,
47
+ options: {},
48
+ },
49
+ lint: {
50
+ logLevel: "error",
51
+ },
52
+ extensions: [],
53
+ files: {
54
+ css: {
55
+ abbr: "css",
56
+ name: "index",
57
+ extension: "css",
58
+ },
59
+ js: {
60
+ abbr: "js",
61
+ name: "index",
62
+ extension: "js",
63
+ },
64
+ mocks: {
65
+ abbr: "mocks",
66
+ name: "mocks",
67
+ extension: ["json", "js"],
68
+ },
69
+ schema: {
70
+ abbr: "schema",
71
+ name: "schema",
72
+ extension: "json",
73
+ },
74
+ templates: {
75
+ abbr: "tpl",
76
+ name: "index",
77
+ },
78
+ },
79
+ namespaces: {},
80
+ projectName: "miyagi",
81
+ ui: {
82
+ mode: "light",
83
+ lang: "en",
84
+ reload: true,
85
+ reloadAfterChanges: {
86
+ componentAssets: true,
87
+ },
88
+ textDirection: "ltr",
89
+ theme: {
90
+ css: null,
91
+ favicon: null,
92
+ js: null,
93
+ logo: {
94
+ light: null,
95
+ dark: null,
96
+ },
97
+ },
98
+ watchConfigFile: true,
99
+ },
100
+ watch: {
101
+ enabled: true,
102
+ backend: "chokidar",
103
+ sources: [],
104
+ ignore: {
105
+ defaults: true,
106
+ patterns: [],
107
+ },
108
+ behavior: {
109
+ startupGraceMs: 500,
110
+ debounceMs: 60,
111
+ coalesceWindowMs: 120,
112
+ awaitWriteFinish: {
113
+ enabled: true,
114
+ stabilityThresholdMs: 200,
115
+ pollIntervalMs: 50,
116
+ },
117
+ },
118
+ reload: {
119
+ enabled: true,
120
+ rules: {
121
+ template: "iframe",
122
+ data: "parent",
123
+ docs: "parent",
124
+ schema: "iframe",
125
+ componentAsset: "iframe",
126
+ globalCss: "iframe",
127
+ globalJs: "iframe",
128
+ unknown: "parent",
129
+ },
130
+ },
131
+ socket: {
132
+ reconnect: {
133
+ enabled: true,
134
+ initialDelayMs: 250,
135
+ maxDelayMs: 5000,
136
+ jitter: true,
137
+ },
138
+ heartbeat: {
139
+ enabled: true,
140
+ intervalMs: 30000,
141
+ },
142
+ },
143
+ report: {
144
+ enabled: true,
145
+ onStart: true,
146
+ format: "summary",
147
+ destination: "stdout",
148
+ useColors: true,
149
+ },
150
+ configFile: {
151
+ enabled: true,
152
+ },
153
+ debug: {
154
+ logEvents: false,
155
+ logDecisions: false,
156
+ logResolvedSources: false,
157
+ },
158
+ },
159
+ schema: {
160
+ ajv: AJV,
161
+ verbose: false,
162
+ },
163
+ schemaValidationMode: "collect-all",
164
+ },
165
+ projectName: "miyagi",
166
+ defaultPort: 5000,
167
+ folders: {
168
+ assets: {
169
+ development: "frontend/assets",
170
+ production: "dist",
171
+ },
172
+ },
173
+ defaultVariationName: "default",
173
174
  };
package/lib/errors.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CLI/process exit codes used by miyagi.
3
+ *
4
+ * Keep this list small and coarse-grained. The goal is predictable automation,
5
+ * not a unique code for every possible failure.
6
+ */
7
+ export const EXIT_CODES = Object.freeze({
8
+ SUCCESS: 0,
9
+ GENERAL_ERROR: 1,
10
+ CLI_USAGE_ERROR: 2,
11
+ CONFIG_ERROR: 3,
12
+ VALIDATION_ERROR: 4,
13
+ });
14
+
15
+ export class MiyagiError extends Error {
16
+ /**
17
+ * @param {string} message
18
+ * @param {object} [options]
19
+ * @param {number} [options.code] Exit code from `EXIT_CODES`.
20
+ * @param {boolean} [options.logged] True if this error was already sent to the logger and should not be logged again at the CLI boundary.
21
+ */
22
+ constructor(
23
+ message,
24
+ { code = EXIT_CODES.GENERAL_ERROR, logged = false } = {},
25
+ ) {
26
+ super(message);
27
+ this.name = "MiyagiError";
28
+ this.code = code;
29
+ this.logged = logged;
30
+ }
31
+ }