@schalkneethling/miyagi-core 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/init/args.js CHANGED
@@ -6,101 +6,169 @@
6
6
  import yargs from "yargs";
7
7
  import { hideBin } from "yargs/helpers";
8
8
  import pkgJson from "../../package.json" with { type: "json" };
9
+ import { EXIT_CODES, MiyagiError } from "../errors.js";
9
10
 
10
- export default yargs(hideBin(process.argv))
11
- .command("start", "Starts the miyagi server", {
12
- verbose: {
11
+ /**
12
+ * @param {object} handlers
13
+ * @param {string[]} [argv]
14
+ * @returns {object}
15
+ */
16
+ export default function createCli(handlers, argv = process.argv) {
17
+ let result;
18
+
19
+ const commandHandler = (handler) => async (args) => {
20
+ result = await handler(args);
21
+ return result;
22
+ };
23
+
24
+ const cli = yargs(hideBin(argv))
25
+ .scriptName("miyagi")
26
+ .option("verbose", {
27
+ alias: "v",
13
28
  description:
14
29
  "Logging additional information — helpful mainly in case of errors.",
15
30
  type: "boolean",
31
+ global: true,
32
+ })
33
+ .command(
34
+ "start",
35
+ "Starts the miyagi server",
36
+ (builder) =>
37
+ builder
38
+ .option("watch-report", {
39
+ description: "Enable watch report output on startup.",
40
+ type: "boolean",
41
+ })
42
+ .option("watch-report-format", {
43
+ description: "Set watch report format.",
44
+ type: "string",
45
+ choices: ["pretty", "summary", "json"],
46
+ })
47
+ .option("watch-report-no-color", {
48
+ description: "Disable colors in watch report output.",
49
+ type: "boolean",
50
+ }),
51
+ commandHandler(handlers.start),
52
+ )
53
+ .command(
54
+ "build",
55
+ "Creates a static build of all your components",
56
+ (builder) =>
57
+ builder.option("folder", {
58
+ description: "The folder where your static build files will be saved",
59
+ type: "string",
60
+ }),
61
+ commandHandler(handlers.build),
62
+ )
63
+ .command(
64
+ "new <component>",
65
+ "Creates a new component folder (including template, CSS, JS, documentation, mocks, and schema files)",
66
+ (builder) =>
67
+ builder
68
+ .positional("component", {
69
+ description: "The component path to create",
70
+ type: "string",
71
+ })
72
+ .option("skip", {
73
+ description:
74
+ "files that will not be created\n(space separated list of tpl, css, js, docs, mocks, schema)",
75
+ type: "array",
76
+ })
77
+ .option("only", {
78
+ description:
79
+ "tells miyagi to only created the passes file types\n(space separated list of tpl, css, js, docs, mocks, schema)",
80
+ type: "array",
81
+ }),
82
+ commandHandler(handlers.new),
83
+ )
84
+ .command(
85
+ "mocks <component>",
86
+ "Creates a mock data file with dummy content based on the schema file",
87
+ (builder) =>
88
+ builder.positional("component", {
89
+ description: "The component path to generate mock data for",
90
+ type: "string",
91
+ }),
92
+ commandHandler(handlers.mocks),
93
+ )
94
+ .command(
95
+ "lint [component]",
96
+ "Validates if the component's mock data matches its JSON schema",
97
+ (builder) =>
98
+ builder.positional("component", {
99
+ description: "Optional component path to lint",
100
+ type: "string",
101
+ }),
102
+ commandHandler(handlers.lint),
103
+ )
104
+ .command(
105
+ "drupal-assets",
106
+ "Resolves Drupal *.libraries.yml dependencies and updates component $assets in mock files",
107
+ (builder) =>
108
+ builder
109
+ .option("engine", {
110
+ alias: "e",
111
+ description: "Engine to use for asset resolution",
112
+ type: "string",
113
+ choices: ["drupal"],
114
+ default: "drupal",
115
+ })
116
+ .option("config", {
117
+ description: "Path to .miyagi-assets.js config file",
118
+ type: "string",
119
+ default: ".miyagi-assets.js",
120
+ })
121
+ .option("libraries", {
122
+ alias: "l",
123
+ description: "Path to *.libraries.yml (overrides config)",
124
+ type: "string",
125
+ })
126
+ .option("components", {
127
+ alias: "c",
128
+ description: "Library names to process (space-separated)",
129
+ type: "array",
130
+ })
131
+ .option("ignore-prefixes", {
132
+ description:
133
+ 'Dependency prefixes to skip (e.g. "core" to ignore core/jquery)',
134
+ type: "array",
135
+ })
136
+ .option("dry-run", {
137
+ description: "Print resolved $assets without writing files",
138
+ type: "boolean",
139
+ default: false,
140
+ }),
141
+ commandHandler(handlers.drupalAssets),
142
+ )
143
+ .command(
144
+ "doctor",
145
+ "Checks your miyagi environment and config for common setup issues",
146
+ () => {},
147
+ commandHandler(handlers.doctor),
148
+ )
149
+ .help()
150
+ .version(pkgJson.version)
151
+ .alias("help", "h")
152
+ .strictCommands()
153
+ .demandCommand()
154
+ .exitProcess(false)
155
+ .fail((message, error) => {
156
+ if (error) {
157
+ throw error;
158
+ }
159
+
160
+ throw new MiyagiError(message || "CLI parsing failed.", {
161
+ code: EXIT_CODES.CLI_USAGE_ERROR,
162
+ });
163
+ })
164
+ .epilogue(
165
+ "Please check https://docs.miyagi.dev/configuration/options/ for all options",
166
+ );
167
+
168
+ return {
169
+ cli,
170
+ getResult() {
171
+ return result;
16
172
  },
17
- "watch-report": {
18
- description: "Enable watch report output on startup.",
19
- type: "boolean",
20
- },
21
- "watch-report-format": {
22
- description: "Set watch report format.",
23
- type: "string",
24
- choices: ["pretty", "summary", "json"],
25
- },
26
- "watch-report-no-color": {
27
- description: "Disable colors in watch report output.",
28
- type: "boolean",
29
- },
30
- })
31
- .command("build", "Creates a static build of all your components", {
32
- folder: {
33
- description: "The folder where your static build files will be saved",
34
- type: "string",
35
- },
36
- })
37
- .command(
38
- "new",
39
- "Creates a new component folder (including template, CSS, JS, documentation, mocks, and schema files)",
40
- {
41
- skip: {
42
- description:
43
- "files that will not be created\n(space separated list of tpl, css, js, docs, mocks, schema)",
44
- type: "array",
45
- },
46
- only: {
47
- description:
48
- "tells miyagi to only created the passes file types\n(space separated list of tpl, css, js, docs, mocks, schema)",
49
- type: "array",
50
- },
51
- },
52
- )
53
- .command(
54
- "mocks",
55
- "Creates a mock data file with dummy content based on the schema file",
56
- )
57
- .command(
58
- "lint",
59
- "Validates if the component's mock data matches its JSON schema",
60
- )
61
- .command(
62
- "drupal-assets",
63
- "Resolves Drupal *.libraries.yml dependencies and updates component $assets in mock files",
64
- {
65
- engine: {
66
- alias: "e",
67
- description: "Engine to use for asset resolution",
68
- type: "string",
69
- choices: ["drupal"],
70
- default: "drupal",
71
- },
72
- config: {
73
- description: "Path to .miyagi-assets.js config file",
74
- type: "string",
75
- default: ".miyagi-assets.js",
76
- },
77
- libraries: {
78
- alias: "l",
79
- description: "Path to *.libraries.yml (overrides config)",
80
- type: "string",
81
- },
82
- components: {
83
- alias: "c",
84
- description: "Library names to process (space-separated)",
85
- type: "array",
86
- },
87
- "ignore-prefixes": {
88
- description:
89
- 'Dependency prefixes to skip (e.g. "core" to ignore core/jquery)',
90
- type: "array",
91
- },
92
- "dry-run": {
93
- description: "Print resolved $assets without writing files",
94
- type: "boolean",
95
- default: false,
96
- },
97
- },
98
- )
99
- .help()
100
- .version(pkgJson.version)
101
- .alias("help", "h")
102
- .alias("verbose", "v")
103
- .demandCommand()
104
- .epilogue(
105
- "Please check https://docs.miyagi.dev/configuration/options/ for all options",
106
- );
173
+ };
174
+ }
@@ -7,6 +7,7 @@ import fs from "fs";
7
7
  import { t } from "../i18n/index.js";
8
8
  import log from "../logger.js";
9
9
  import * as helpers from "../helpers.js";
10
+ import { EXIT_CODES, MiyagiError } from "../errors.js";
10
11
  import TwingCache from "./twing/cache.js";
11
12
  import * as twingFunctions from "./twing/functions.js";
12
13
 
@@ -41,8 +42,12 @@ async function setUserEngine() {
41
42
  const { engine } = global.config;
42
43
 
43
44
  if (!engine.render) {
44
- log("error", "No render function has beend defined.");
45
- process.exit(1);
45
+ const message = "No render function has beend defined.";
46
+ log("error", message);
47
+ throw new MiyagiError(message, {
48
+ code: EXIT_CODES.CONFIG_ERROR,
49
+ logged: true,
50
+ });
46
51
  }
47
52
 
48
53
  try {
@@ -51,8 +56,12 @@ async function setUserEngine() {
51
56
  async (name, context, cb) => await engine.render({ name, context, cb }),
52
57
  );
53
58
  } catch (e) {
54
- log("error", t("settingEngineFailed"), e);
55
- process.exit(1);
59
+ const message = t("settingEngineFailed");
60
+ log("error", message, e);
61
+ throw new MiyagiError(message, {
62
+ code: EXIT_CODES.CONFIG_ERROR,
63
+ logged: true,
64
+ });
56
65
  }
57
66
  }
58
67
 
package/lib/init/index.js CHANGED
@@ -12,6 +12,7 @@ import appConfig from "../default-config.js";
12
12
  import { t } from "../i18n/index.js";
13
13
  import build from "../build/index.js";
14
14
  import log from "../logger.js";
15
+ import { EXIT_CODES } from "../errors.js";
15
16
  import setEngines from "./engines.js";
16
17
  import setRouter from "./router.js";
17
18
  import setState from "../state/index.js";
@@ -50,15 +51,24 @@ export default async function init(mergedConfig) {
50
51
  setViews();
51
52
 
52
53
  if (global.config.isBuild) {
53
- return build()
54
- .then((message) => {
55
- log("success", message);
56
- process.exit(0);
57
- })
58
- .catch((error) => {
59
- log("error", error);
60
- process.exit(1);
61
- });
54
+ try {
55
+ const message = await build();
56
+ log("success", message);
57
+ return {
58
+ success: true,
59
+ code: EXIT_CODES.SUCCESS,
60
+ shouldExit: true,
61
+ message,
62
+ };
63
+ } catch (error) {
64
+ log("error", error);
65
+ return {
66
+ success: false,
67
+ code: EXIT_CODES.GENERAL_ERROR,
68
+ shouldExit: true,
69
+ message: String(error),
70
+ };
71
+ }
62
72
  }
63
73
 
64
74
  const { server, port: actualPort } = await startServer(
@@ -69,7 +79,13 @@ export default async function init(mergedConfig) {
69
79
 
70
80
  log("success", `${t("serverStarted").replace("{{port}}", actualPort)}\n`);
71
81
 
72
- return server;
82
+ return {
83
+ success: true,
84
+ code: EXIT_CODES.SUCCESS,
85
+ shouldExit: false,
86
+ server,
87
+ port: actualPort,
88
+ };
73
89
  }
74
90
 
75
91
  /**
@@ -161,18 +161,21 @@ function printWatchReport(report, watchConfig) {
161
161
  ` ${colorize("grey", "Diagnostics:", useColors)} sources=${report.meta.sourceCount
162
162
  }, ignores=${report.meta.ignoreCount}`,
163
163
  );
164
- console.info(` ${colorize("grey", "Resolved sources:", useColors)}`);
165
164
 
166
- for (const source of report.sources) {
167
- const stateLabel = source.exists ? "exists" : "missing";
168
- const stateColor = source.exists ? "green" : "yellow";
169
- console.info(
170
- ` - ${source.id} (${source.type}) ${source.resolvedPath} [${colorize(
171
- stateColor,
172
- stateLabel,
173
- useColors,
174
- )}]`,
175
- );
165
+ if (watchConfig?.debug?.logResolvedSources) {
166
+ console.info(` ${colorize("grey", "Resolved sources:", useColors)}`);
167
+
168
+ for (const source of report.sources) {
169
+ const stateLabel = source.exists ? "exists" : "missing";
170
+ const stateColor = source.exists ? "green" : "yellow";
171
+ console.info(
172
+ ` - ${source.id} (${source.type}) ${source.resolvedPath} [${colorize(
173
+ stateColor,
174
+ stateLabel,
175
+ useColors,
176
+ )}]`,
177
+ );
178
+ }
176
179
  }
177
180
 
178
181
  console.info(` ${colorize("grey", "Ignored patterns:", useColors)}`);
@@ -628,6 +631,7 @@ export default function Watcher(server) {
628
631
 
629
632
  isProcessing = true;
630
633
  try {
634
+ log("info", t("updatingStarted"));
631
635
  const events = snapshotPendingEvents(pendingByPath);
632
636
  pendingByPath.clear();
633
637
  await handleFileChange(events);
@@ -713,7 +717,6 @@ export default function Watcher(server) {
713
717
  log("info", `watch:event=${eventType} path=${changedPath}`);
714
718
  }
715
719
 
716
- log("info", t("updatingStarted"));
717
720
  enqueueEvent(eventType, changedPath);
718
721
  });
719
722
 
@@ -7,6 +7,7 @@ import path from "path";
7
7
  import log from "../logger.js";
8
8
  import { t } from "../i18n/index.js";
9
9
  import { readdir } from "node:fs/promises";
10
+ import { EXIT_CODES, MiyagiError } from "../errors.js";
10
11
 
11
12
  /**
12
13
  * @param {string|null} dir - the directory in which to look for files
@@ -25,17 +26,21 @@ async function getFiles(dir, ignores, configPath, type, check) {
25
26
  });
26
27
  } catch (error) {
27
28
  if (error.code === "ENOENT") {
28
- log(
29
- "error",
30
- t("srcFolderNotFound")
31
- .replaceAll("{{directory}}", dir)
32
- .replaceAll("{{type}}", type)
33
- .replaceAll("{{config}}", configPath),
34
- );
35
- process.exit(1);
29
+ const message = t("srcFolderNotFound")
30
+ .replaceAll("{{directory}}", dir)
31
+ .replaceAll("{{type}}", type)
32
+ .replaceAll("{{config}}", configPath);
33
+ log("error", message);
34
+ throw new MiyagiError(message, {
35
+ code: EXIT_CODES.CONFIG_ERROR,
36
+ logged: true,
37
+ });
36
38
  } else {
37
39
  log("error", error.toString(), error);
38
- process.exit(1);
40
+ throw new MiyagiError(error.toString(), {
41
+ code: EXIT_CODES.GENERAL_ERROR,
42
+ logged: true,
43
+ });
39
44
  }
40
45
  }
41
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schalkneethling/miyagi-core",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "miyagi is a component development tool for JavaScript template engines.",
5
5
  "main": "index.js",
6
6
  "author": "Schalk Neethling <schalkneethling@duck.com>, Michael Großklaus <mail@mgrossklaus.de> (https://www.mgrossklaus.de)",
@@ -53,7 +53,7 @@
53
53
  "devDependencies": {
54
54
  "@eslint/js": "^9.39.2",
55
55
  "@rollup/plugin-node-resolve": "^16.0.3",
56
- "@rollup/plugin-terser": "^0.4.4",
56
+ "@rollup/plugin-terser": "^1.0.0",
57
57
  "@types/js-yaml": "^4.0.9",
58
58
  "@types/node": "^25.3.3",
59
59
  "@types/yargs": "^17.0.35",
@@ -61,7 +61,7 @@
61
61
  "cssnano": "^7.1.2",
62
62
  "eslint": "^9.39.0",
63
63
  "eslint-plugin-jsdoc": "^62.5.4",
64
- "globals": "^15.15.0",
64
+ "globals": "^17.4.0",
65
65
  "gulp": "^5.0.1",
66
66
  "gulp-postcss": "^10.0.0",
67
67
  "postcss": "^8.5.6",