@jant/core 0.3.46 → 0.3.47

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 (109) hide show
  1. package/bin/commands/db/execute-file.js +12 -4
  2. package/bin/commands/db/rehearse.js +2 -2
  3. package/bin/commands/export.js +12 -4
  4. package/bin/commands/import-site.js +60 -267
  5. package/bin/commands/migrate.js +36 -69
  6. package/bin/commands/reset-password.js +10 -4
  7. package/bin/commands/site/export.js +59 -248
  8. package/bin/commands/site/snapshot/export.js +58 -45
  9. package/bin/commands/site/snapshot/import.js +104 -52
  10. package/bin/lib/node-env.js +100 -0
  11. package/bin/lib/runtime-target.js +64 -0
  12. package/bin/lib/site-snapshot.js +185 -54
  13. package/bin/lib/sql-export.js +19 -2
  14. package/dist/{app-DB-P66E5.js → app-3REcR-3U.js} +331 -189
  15. package/dist/app-B67XOEyo.js +6 -0
  16. package/dist/client/.vite/manifest.json +2 -2
  17. package/dist/client/_assets/{client-auth-BLCUje4M.js → client-auth-Ce5WEAVS.js} +102 -49
  18. package/dist/client/_assets/client-s71Js1Cu.css +2 -0
  19. package/dist/{github-sync-CQ1x271f.js → export-ZBlfKSKm.js} +12 -439
  20. package/dist/github-sync-C593r22F.js +4 -0
  21. package/dist/github-sync-bL1hnx3Q.js +428 -0
  22. package/dist/index.js +3 -2
  23. package/dist/node.js +5 -4
  24. package/package.json +3 -2
  25. package/src/__tests__/helpers/export-fixtures.ts +0 -1
  26. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -0
  27. package/src/client/components/__tests__/jant-settings-general.test.ts +70 -0
  28. package/src/client/components/jant-settings-general.ts +164 -22
  29. package/src/client/components/settings-types.ts +4 -6
  30. package/src/client-auth.ts +1 -1
  31. package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
  32. package/src/db/__tests__/migration-rehearsal.test.ts +2 -5
  33. package/src/db/backfills/0004_register_apple_touch_media_rows.sql +65 -0
  34. package/src/db/migrations/0021_thankful_phalanx.sql +16 -0
  35. package/src/db/migrations/meta/0021_snapshot.json +2121 -0
  36. package/src/db/migrations/meta/_journal.json +7 -0
  37. package/src/db/migrations/pg/0019_gray_natasha_romanoff.sql +20 -0
  38. package/src/db/migrations/pg/meta/0019_snapshot.json +2718 -0
  39. package/src/db/migrations/pg/meta/_journal.json +7 -0
  40. package/src/db/pg/schema.ts +21 -26
  41. package/src/db/rehearsal-fixtures/demo-current.json +1 -1
  42. package/src/db/schema.ts +16 -20
  43. package/src/i18n/__tests__/middleware.test.ts +43 -1
  44. package/src/i18n/coverage.generated.ts +17 -0
  45. package/src/i18n/i18n.ts +18 -2
  46. package/src/i18n/index.ts +3 -0
  47. package/src/i18n/locales/settings/en.po +16 -11
  48. package/src/i18n/locales/settings/en.ts +1 -1
  49. package/src/i18n/locales/settings/zh-Hans.po +17 -12
  50. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  51. package/src/i18n/locales/settings/zh-Hant.po +16 -11
  52. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  53. package/src/i18n/locales.ts +84 -2
  54. package/src/i18n/middleware.ts +25 -16
  55. package/src/i18n/supported-locales.ts +153 -0
  56. package/src/lib/__tests__/csp-builder.test.ts +19 -2
  57. package/src/lib/__tests__/feed.test.ts +242 -1
  58. package/src/lib/__tests__/post-meta.test.ts +0 -1
  59. package/src/lib/__tests__/view.test.ts +0 -1
  60. package/src/lib/csp-builder.ts +28 -10
  61. package/src/lib/feed.ts +153 -3
  62. package/src/middleware/__tests__/secure-headers.test.ts +89 -0
  63. package/src/middleware/auth.ts +1 -1
  64. package/src/middleware/secure-headers.ts +47 -1
  65. package/src/node/__tests__/cli-runtime-target.test.ts +110 -2
  66. package/src/node/__tests__/cli-site-snapshot.test.ts +308 -13
  67. package/src/node/__tests__/cli-site-token-env.test.ts +2 -7
  68. package/src/node/__tests__/cli-snapshot-meta.test.ts +85 -0
  69. package/src/node/__tests__/cli-sql-export.test.ts +49 -0
  70. package/src/node/index.ts +1 -0
  71. package/src/preset.css +8 -2
  72. package/src/routes/api/__tests__/settings.test.ts +3 -2
  73. package/src/routes/api/github-sync.tsx +1 -1
  74. package/src/routes/api/settings.ts +4 -1
  75. package/src/routes/auth/signin.tsx +6 -0
  76. package/src/routes/pages/archive.tsx +4 -2
  77. package/src/services/__tests__/post.test.ts +19 -19
  78. package/src/services/__tests__/search.test.ts +0 -1
  79. package/src/services/__tests__/settings.test.ts +22 -3
  80. package/src/services/bootstrap.ts +7 -3
  81. package/src/services/collection.ts +3 -3
  82. package/src/services/export.ts +0 -3
  83. package/src/services/navigation.ts +0 -2
  84. package/src/services/path.ts +1 -38
  85. package/src/services/post.ts +32 -66
  86. package/src/services/search.ts +0 -6
  87. package/src/services/settings.ts +47 -6
  88. package/src/services/site-admin.ts +6 -1
  89. package/src/styles/ui.css +12 -23
  90. package/src/types/entities.ts +0 -1
  91. package/src/ui/color-themes.ts +1 -1
  92. package/src/ui/dash/settings/GeneralContent.tsx +17 -19
  93. package/src/ui/dash/settings/SettingsRootContent.tsx +17 -28
  94. package/src/ui/feed/NoteCard.tsx +1 -11
  95. package/src/ui/feed/__tests__/timeline-cards.test.ts +1 -1
  96. package/src/ui/pages/PostPage.tsx +2 -0
  97. package/bin/commands/collections.js +0 -268
  98. package/bin/commands/media.js +0 -302
  99. package/bin/commands/posts.js +0 -262
  100. package/bin/commands/search.js +0 -53
  101. package/bin/commands/settings.js +0 -93
  102. package/bin/lib/http-api.js +0 -223
  103. package/bin/lib/media-upload.js +0 -206
  104. package/dist/app-CM7sb3xO.js +0 -5
  105. package/dist/client/_assets/client-DDs6NzB3.css +0 -2
  106. package/src/__tests__/bin/content-cli.test.ts +0 -179
  107. package/src/__tests__/bin/media-cli.test.ts +0 -192
  108. /package/dist/{github-api-BkRWnqMx.js → github-api-Bh0PH3zr.js} +0 -0
  109. /package/dist/{github-app-WeadXMb8.js → github-app-D0GvNnqp.js} +0 -0
@@ -1,262 +0,0 @@
1
- import { parseArgs } from "node:util";
2
- import {
3
- printJson,
4
- readJsonInput,
5
- requestJson,
6
- requireApiToken,
7
- requireSiteUrl,
8
- runCommand,
9
- sharedApiOptions,
10
- } from "../lib/http-api.js";
11
-
12
- function showHelp() {
13
- console.log("Usage: jant posts <subcommand> [options]");
14
- console.log("");
15
- console.log("Subcommands:");
16
- console.log(" list List posts");
17
- console.log(" get <id> Get one post");
18
- console.log(" content <id> Get one post body as markdown");
19
- console.log(" create Create a post from JSON");
20
- console.log(" update <id> Update a post from JSON");
21
- console.log(" delete <id> Delete a post");
22
- console.log("");
23
- console.log("Create and update accept either:");
24
- console.log(" --json '{...}' Inline JSON body");
25
- console.log(" --input <path> Path to a JSON file");
26
- console.log(" --input - Read JSON from stdin");
27
- console.log("");
28
- console.log("Shared options:");
29
- console.log(" --url Target site URL");
30
- console.log(" --token API token");
31
- console.log(
32
- " --config Wrangler config file (default: wrangler.toml)",
33
- );
34
- console.log(" --env Wrangler environment name");
35
- console.log("");
36
- console.log("Authentication:");
37
- console.log(" Posts commands require an API token.");
38
- console.log(" Use JANT_API_TOKEN or DEV_API_TOKEN for local development.");
39
- }
40
-
41
- export async function run(argv) {
42
- return runCommand(async () => {
43
- const [subcommand, ...rest] = argv;
44
-
45
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
46
- showHelp();
47
- return;
48
- }
49
-
50
- switch (subcommand) {
51
- case "list":
52
- await runList(rest);
53
- return;
54
- case "get":
55
- await runGet(rest);
56
- return;
57
- case "content":
58
- await runContent(rest);
59
- return;
60
- case "create":
61
- await runCreate(rest);
62
- return;
63
- case "update":
64
- await runUpdate(rest);
65
- return;
66
- case "delete":
67
- await runDelete(rest);
68
- return;
69
- default:
70
- throw new Error(`Unknown posts subcommand: ${subcommand}`);
71
- }
72
- });
73
- }
74
-
75
- async function runList(argv) {
76
- const { values } = parseArgs({
77
- args: argv,
78
- allowPositionals: false,
79
- options: {
80
- ...sharedApiOptions,
81
- cursor: { type: "string" },
82
- format: { type: "string" },
83
- limit: { type: "string" },
84
- status: { type: "string" },
85
- },
86
- });
87
-
88
- if (values.help) {
89
- console.log("Usage: jant posts list [options]");
90
- console.log("");
91
- console.log("Options:");
92
- console.log(" --format note | link | quote");
93
- console.log(" --status draft | published");
94
- console.log(" --limit Max posts to return (1-100)");
95
- console.log(" --cursor Cursor from nextCursor");
96
- return;
97
- }
98
-
99
- const siteUrl = requireSiteUrl(values, "Listing posts");
100
- const token = requireApiToken(values, "Listing posts");
101
-
102
- const result = await requestJson({
103
- siteUrl,
104
- path: "/api/posts",
105
- token,
106
- query: {
107
- cursor: values.cursor,
108
- format: values.format,
109
- limit: values.limit,
110
- status: values.status,
111
- },
112
- });
113
-
114
- printJson(result);
115
- }
116
-
117
- async function runGet(argv) {
118
- const { values, positionals } = parseArgs({
119
- args: argv,
120
- allowPositionals: true,
121
- options: sharedApiOptions,
122
- });
123
-
124
- if (values.help) {
125
- console.log("Usage: jant posts get <id> [options]");
126
- return;
127
- }
128
-
129
- const postId = positionals[0];
130
- if (!postId) {
131
- throw new Error("Post ID is required.");
132
- }
133
-
134
- const siteUrl = requireSiteUrl(values, "Getting a post");
135
- const token = requireApiToken(values, "Getting a post");
136
- const result = await requestJson({
137
- siteUrl,
138
- path: `/api/posts/${postId}`,
139
- token,
140
- });
141
- printJson(result);
142
- }
143
-
144
- async function runContent(argv) {
145
- const { values, positionals } = parseArgs({
146
- args: argv,
147
- allowPositionals: true,
148
- options: sharedApiOptions,
149
- });
150
-
151
- if (values.help) {
152
- console.log("Usage: jant posts content <id> [options]");
153
- return;
154
- }
155
-
156
- const postId = positionals[0];
157
- if (!postId) {
158
- throw new Error("Post ID is required.");
159
- }
160
-
161
- const siteUrl = requireSiteUrl(values, "Getting post content");
162
- const token = requireApiToken(values, "Getting post content");
163
- const result = await requestJson({
164
- siteUrl,
165
- path: `/api/posts/${postId}/content`,
166
- token,
167
- });
168
- printJson(result);
169
- }
170
-
171
- async function runCreate(argv) {
172
- const { values } = parseArgs({
173
- args: argv,
174
- allowPositionals: false,
175
- options: {
176
- ...sharedApiOptions,
177
- input: { type: "string" },
178
- json: { type: "string" },
179
- },
180
- });
181
-
182
- if (values.help) {
183
- console.log("Usage: jant posts create (--json '{...}' | --input <path>)");
184
- return;
185
- }
186
-
187
- const siteUrl = requireSiteUrl(values, "Creating a post");
188
- const token = requireApiToken(values, "Creating a post");
189
- const body = await readJsonInput(values);
190
- const result = await requestJson({
191
- siteUrl,
192
- path: "/api/posts",
193
- method: "POST",
194
- token,
195
- body,
196
- });
197
- printJson(result);
198
- }
199
-
200
- async function runUpdate(argv) {
201
- const { values, positionals } = parseArgs({
202
- args: argv,
203
- allowPositionals: true,
204
- options: {
205
- ...sharedApiOptions,
206
- input: { type: "string" },
207
- json: { type: "string" },
208
- },
209
- });
210
-
211
- if (values.help) {
212
- console.log(
213
- "Usage: jant posts update <id> (--json '{...}' | --input <path>)",
214
- );
215
- return;
216
- }
217
-
218
- const postId = positionals[0];
219
- if (!postId) {
220
- throw new Error("Post ID is required.");
221
- }
222
-
223
- const siteUrl = requireSiteUrl(values, "Updating a post");
224
- const token = requireApiToken(values, "Updating a post");
225
- const body = await readJsonInput(values);
226
- const result = await requestJson({
227
- siteUrl,
228
- path: `/api/posts/${postId}`,
229
- method: "PUT",
230
- token,
231
- body,
232
- });
233
- printJson(result);
234
- }
235
-
236
- async function runDelete(argv) {
237
- const { values, positionals } = parseArgs({
238
- args: argv,
239
- allowPositionals: true,
240
- options: sharedApiOptions,
241
- });
242
-
243
- if (values.help) {
244
- console.log("Usage: jant posts delete <id> [options]");
245
- return;
246
- }
247
-
248
- const postId = positionals[0];
249
- if (!postId) {
250
- throw new Error("Post ID is required.");
251
- }
252
-
253
- const siteUrl = requireSiteUrl(values, "Deleting a post");
254
- const token = requireApiToken(values, "Deleting a post");
255
- const result = await requestJson({
256
- siteUrl,
257
- path: `/api/posts/${postId}`,
258
- method: "DELETE",
259
- token,
260
- });
261
- printJson(result);
262
- }
@@ -1,53 +0,0 @@
1
- import { parseArgs } from "node:util";
2
- import {
3
- getOptionalApiToken,
4
- printJson,
5
- requestJson,
6
- requireSiteUrl,
7
- runCommand,
8
- sharedApiOptions,
9
- } from "../lib/http-api.js";
10
-
11
- function showHelp() {
12
- console.log("Usage: jant search [--query <text>] [options]");
13
- console.log("");
14
- console.log("Options:");
15
- console.log(" --query Search query");
16
- console.log(" --limit Max results to return (1-50)");
17
- }
18
-
19
- export async function run(argv) {
20
- return runCommand(async () => {
21
- const { values, positionals } = parseArgs({
22
- args: argv,
23
- allowPositionals: true,
24
- options: {
25
- ...sharedApiOptions,
26
- limit: { type: "string" },
27
- query: { type: "string" },
28
- },
29
- });
30
-
31
- if (values.help) {
32
- showHelp();
33
- return;
34
- }
35
-
36
- const query = values.query?.trim() || positionals.join(" ").trim();
37
- if (!query) {
38
- throw new Error("Search query is required.");
39
- }
40
-
41
- const siteUrl = requireSiteUrl(values, "Searching posts");
42
- const result = await requestJson({
43
- siteUrl,
44
- path: "/api/search",
45
- token: getOptionalApiToken(values),
46
- query: {
47
- limit: values.limit,
48
- q: query,
49
- },
50
- });
51
- printJson(result);
52
- });
53
- }
@@ -1,93 +0,0 @@
1
- import { parseArgs } from "node:util";
2
- import {
3
- printJson,
4
- readJsonInput,
5
- requestJson,
6
- requireApiToken,
7
- requireSiteUrl,
8
- runCommand,
9
- sharedApiOptions,
10
- } from "../lib/http-api.js";
11
-
12
- function showHelp() {
13
- console.log("Usage: jant settings <subcommand> [options]");
14
- console.log("");
15
- console.log("Subcommands:");
16
- console.log(" get Get editable site settings");
17
- console.log(" update Update editable site settings from JSON");
18
- }
19
-
20
- export async function run(argv) {
21
- return runCommand(async () => {
22
- const [subcommand, ...rest] = argv;
23
-
24
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
25
- showHelp();
26
- return;
27
- }
28
-
29
- switch (subcommand) {
30
- case "get":
31
- await runGet(rest);
32
- return;
33
- case "update":
34
- await runUpdate(rest);
35
- return;
36
- default:
37
- throw new Error(`Unknown settings subcommand: ${subcommand}`);
38
- }
39
- });
40
- }
41
-
42
- async function runGet(argv) {
43
- const { values } = parseArgs({
44
- args: argv,
45
- allowPositionals: false,
46
- options: sharedApiOptions,
47
- });
48
-
49
- if (values.help) {
50
- console.log("Usage: jant settings get [options]");
51
- return;
52
- }
53
-
54
- const siteUrl = requireSiteUrl(values, "Getting settings");
55
- const token = requireApiToken(values, "Getting settings");
56
- const result = await requestJson({
57
- siteUrl,
58
- path: "/api/settings",
59
- token,
60
- });
61
- printJson(result);
62
- }
63
-
64
- async function runUpdate(argv) {
65
- const { values } = parseArgs({
66
- args: argv,
67
- allowPositionals: false,
68
- options: {
69
- ...sharedApiOptions,
70
- input: { type: "string" },
71
- json: { type: "string" },
72
- },
73
- });
74
-
75
- if (values.help) {
76
- console.log(
77
- "Usage: jant settings update (--json '{...}' | --input <path>)",
78
- );
79
- return;
80
- }
81
-
82
- const siteUrl = requireSiteUrl(values, "Updating settings");
83
- const token = requireApiToken(values, "Updating settings");
84
- const body = await readJsonInput(values);
85
- const result = await requestJson({
86
- siteUrl,
87
- path: "/api/settings",
88
- method: "PUT",
89
- token,
90
- body,
91
- });
92
- printJson(result);
93
- }
@@ -1,223 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { CLI_API_TOKEN_ENV_VAR, getCliApiToken } from "./cli-api-token.js";
3
- import { resolveSiteUrl } from "./site-url.js";
4
-
5
- export const DEV_API_TOKEN_ENV_VAR = "DEV_API_TOKEN";
6
-
7
- export const sharedApiOptions = {
8
- config: { type: "string" },
9
- env: { type: "string" },
10
- help: { type: "boolean", short: "h" },
11
- token: { type: "string" },
12
- url: { type: "string" },
13
- };
14
-
15
- function normalizeBaseUrl(value) {
16
- const trimmed = value.trim();
17
- return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
18
- }
19
-
20
- function maybeParseJson(text) {
21
- if (!text) {
22
- return null;
23
- }
24
-
25
- try {
26
- return JSON.parse(text);
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
32
- async function readStdinText() {
33
- const chunks = [];
34
- for await (const chunk of process.stdin) {
35
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
36
- }
37
-
38
- return Buffer.concat(chunks).toString("utf8");
39
- }
40
-
41
- export function buildApiUrl(siteUrl, path, query) {
42
- const url = new URL(path.replace(/^\//, ""), normalizeBaseUrl(siteUrl));
43
-
44
- if (query) {
45
- for (const [key, value] of Object.entries(query)) {
46
- if (value === undefined || value === null || value === "") {
47
- continue;
48
- }
49
- url.searchParams.set(key, String(value));
50
- }
51
- }
52
-
53
- return url.toString();
54
- }
55
-
56
- export function resolveRequestUrl(siteUrl, targetUrl) {
57
- return new URL(targetUrl, normalizeBaseUrl(siteUrl)).toString();
58
- }
59
-
60
- export function requireSiteUrl(values, purpose) {
61
- const siteUrl = resolveSiteUrl({
62
- url: values.url,
63
- config: values.config,
64
- env: values.env,
65
- });
66
-
67
- if (!siteUrl) {
68
- throw new Error(
69
- `${purpose} requires --url or SITE_ORIGIN in the environment or wrangler.toml.`,
70
- );
71
- }
72
-
73
- return siteUrl;
74
- }
75
-
76
- export function getOptionalApiToken(values) {
77
- return (
78
- values.token?.trim() ||
79
- getCliApiToken(process.env, process.env[DEV_API_TOKEN_ENV_VAR]?.trim()) ||
80
- ""
81
- );
82
- }
83
-
84
- export function requireApiToken(values, purpose) {
85
- const token = getOptionalApiToken(values);
86
-
87
- if (!token) {
88
- throw new Error(
89
- `${purpose} requires --token or ${CLI_API_TOKEN_ENV_VAR} (or ${DEV_API_TOKEN_ENV_VAR} for local development).`,
90
- );
91
- }
92
-
93
- return token;
94
- }
95
-
96
- export async function readJsonInput(values) {
97
- const rawJson = values.json?.trim();
98
- const inputPath = values.input?.trim();
99
-
100
- if (rawJson && inputPath) {
101
- throw new Error("Provide either --json or --input, not both.");
102
- }
103
-
104
- if (!rawJson && !inputPath) {
105
- throw new Error("Provide --json or --input.");
106
- }
107
-
108
- const source =
109
- rawJson ??
110
- (inputPath === "-"
111
- ? await readStdinText()
112
- : await readFile(inputPath, "utf8"));
113
-
114
- try {
115
- return JSON.parse(source);
116
- } catch {
117
- const sourceLabel = rawJson
118
- ? "--json"
119
- : inputPath === "-"
120
- ? "stdin"
121
- : inputPath;
122
- throw new Error(`Invalid JSON in ${sourceLabel}.`);
123
- }
124
- }
125
-
126
- export async function requestRaw({
127
- body,
128
- headers: customHeaders,
129
- method = "GET",
130
- path,
131
- query,
132
- siteUrl,
133
- token,
134
- url,
135
- }) {
136
- const requestUrl =
137
- url ?? (siteUrl && path ? buildApiUrl(siteUrl, path, query) : "");
138
- if (!requestUrl) {
139
- throw new Error("requestRaw requires either url or siteUrl + path.");
140
- }
141
-
142
- const headers = {
143
- ...(customHeaders ?? {}),
144
- };
145
-
146
- if (token) {
147
- headers.Authorization = `Bearer ${token}`;
148
- }
149
- const response = await fetch(requestUrl, {
150
- method,
151
- headers,
152
- body,
153
- });
154
-
155
- const text = await response.text();
156
- const parsed = maybeParseJson(text);
157
-
158
- if (!response.ok) {
159
- const message =
160
- parsed &&
161
- typeof parsed === "object" &&
162
- "error" in parsed &&
163
- typeof parsed.error === "string"
164
- ? parsed.error
165
- : text || response.statusText;
166
- throw new Error(`HTTP ${response.status}: ${message}`);
167
- }
168
-
169
- return {
170
- json: parsed,
171
- response,
172
- text,
173
- url: requestUrl,
174
- };
175
- }
176
-
177
- export async function requestJson({
178
- body,
179
- method = "GET",
180
- path,
181
- query,
182
- siteUrl,
183
- token,
184
- url,
185
- }) {
186
- const headers = {
187
- Accept: "application/json",
188
- };
189
- if (body !== undefined) {
190
- headers["Content-Type"] = "application/json";
191
- }
192
-
193
- const { json, text } = await requestRaw({
194
- body: body === undefined ? undefined : JSON.stringify(body),
195
- headers,
196
- method,
197
- path,
198
- query,
199
- siteUrl,
200
- token,
201
- url,
202
- });
203
-
204
- if (!text) {
205
- return null;
206
- }
207
-
208
- return json ?? text;
209
- }
210
-
211
- export function printJson(value) {
212
- console.log(JSON.stringify(value, null, 2));
213
- }
214
-
215
- export async function runCommand(action) {
216
- try {
217
- await action();
218
- } catch (error) {
219
- const message = error instanceof Error ? error.message : String(error);
220
- console.error(`Error: ${message}`);
221
- process.exit(1);
222
- }
223
- }