catalyst-core 1.0.2-canary.1 → 1.0.2

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 (90) hide show
  1. package/bin/catalyst.js +105 -1
  2. package/changelog.md +34 -0
  3. package/dist/cli/README.md +119 -0
  4. package/dist/cli/appConfig.js +51 -0
  5. package/dist/cli/buildErrors.js +170 -0
  6. package/dist/cli/buildWarnings.js +28 -0
  7. package/dist/cli/devOutput.js +524 -0
  8. package/dist/cli/diagnostic.js +388 -0
  9. package/dist/cli/hints.js +69 -0
  10. package/dist/cli/package.json +3 -0
  11. package/dist/cli/spinner.js +141 -0
  12. package/dist/cli/theme.js +155 -0
  13. package/dist/errors/index.js +7 -3
  14. package/dist/logger.js +61 -6
  15. package/dist/native/androidProject/app/build.gradle.kts +2 -1
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +2 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +9 -0
  18. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt +65 -34
  19. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashOverlay.kt +109 -0
  20. package/dist/native/androidSetup.js +46 -57
  21. package/dist/native/buildAndroid/assets.js +22 -13
  22. package/dist/native/buildAndroid/build.js +98 -26
  23. package/dist/native/buildAndroid/config.js +43 -12
  24. package/dist/native/buildAndroid/index.js +49 -191
  25. package/dist/native/buildAppAndroid.js +19 -8
  26. package/dist/native/buildAppIos.js +35 -16
  27. package/dist/native/buildIos/assets.js +1 -6
  28. package/dist/native/buildIos/build.js +66 -95
  29. package/dist/native/buildIos/config.js +26 -8
  30. package/dist/native/buildIos/index.js +143 -64
  31. package/dist/native/buildIos/plugins.js +12 -15
  32. package/dist/native/internalPluginUtils.js +0 -1
  33. package/dist/native/iosnativeWebView/CoreLogic/Sources/CatalystCoreLogic/Constants/ConfigConstants.swift +2 -0
  34. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashHTMLView.swift +31 -0
  35. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +17 -1
  36. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +41 -1
  37. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +2 -0
  38. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +3 -0
  39. package/dist/native/pluginComposerAndroid.js +2 -3
  40. package/dist/native/renameAndroidProject.js +42 -20
  41. package/dist/native/setupEmulatorIos.js +28 -49
  42. package/dist/native/setupServer.js +12 -15
  43. package/dist/native/terminalProgress.js +186 -147
  44. package/dist/native/utils.js +340 -54
  45. package/dist/otel.js +15 -9
  46. package/dist/scripts/build.js +313 -42
  47. package/dist/scripts/generateOfflineManifest.js +11 -1
  48. package/dist/scripts/loadEnvironmentVariables.js +24 -2
  49. package/dist/scripts/preServerInit.js +32 -10
  50. package/dist/scripts/preflight.js +35 -11
  51. package/dist/scripts/scriptUtils.js +34 -0
  52. package/dist/scripts/serve.js +64 -15
  53. package/dist/scripts/start.js +58 -21
  54. package/dist/server/expressServer.js +167 -32
  55. package/dist/server/renderer/handler.jsx +87 -11
  56. package/dist/server/utils/validator.js +14 -11
  57. package/dist/vite/manifest-categorization-plugin.js +7 -5
  58. package/dist/vite/resolveDevServerConfig.js +9 -2
  59. package/dist/vite/vite.config.client.js +18 -0
  60. package/dist/vite/vite.config.js +73 -13
  61. package/dist/vite/vite.config.server.js +21 -0
  62. package/dist/web-router/components/MetaTag.jsx +15 -2
  63. package/dist/web-router/components/MetaTag.test.jsx +71 -2
  64. package/dist/webmcp/WebMcpProvider.jsx +3 -2
  65. package/dist/webmcp/useTool.js +4 -1
  66. package/mcp_v2/conversion-tasks.json +6 -5
  67. package/mcp_v2/knowledge-base.json +124 -7
  68. package/mcp_v2/test/config.test.ts +59 -0
  69. package/mcp_v2/tools/build.js +6 -6
  70. package/mcp_v2/tools/config.js +35 -18
  71. package/mcp_v2/tools/conversion.js +7 -13
  72. package/package.json +5 -5
  73. package/test/cli/buildErrors.test.ts +173 -0
  74. package/test/cli/buildWarnings.test.cjs +62 -0
  75. package/test/cli/devOutput.test.ts +549 -0
  76. package/test/cli/diagnostic.test.ts +434 -0
  77. package/test/cli/output.test.cjs +90 -0
  78. package/test/cli/spinner.test.ts +177 -0
  79. package/test/cli/theme.test.ts +163 -0
  80. package/test/native/redactSecrets.test.ts +72 -0
  81. package/test/native/utils.test.ts +532 -0
  82. package/test/scripts/build.test.ts +275 -0
  83. package/test/scripts/serve.test.ts +102 -0
  84. package/test/server/fixtures/template/src/js/routes/index.jsx +8 -0
  85. package/test/server/handler.test.ts +17 -0
  86. package/test/server/logger.test.ts +185 -0
  87. package/test/server/otel.test.ts +556 -0
  88. package/test/server/validator.test.ts +19 -8
  89. package/dist/scripts/checkVersion.js +0 -3
  90. package/dist/scripts/cleanCache.js +0 -30
@@ -4,196 +4,235 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _ansis = _interopRequireDefault(require("ansis"));
7
+ var _theme = _interopRequireDefault(require("../cli/theme.js"));
8
+ var _spinner = _interopRequireDefault(require("../cli/spinner.js"));
8
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ // Plain ESM imports of a CommonJS module: this file is authored as ESM but
11
+ // babel-compiled to CJS by `npm run prepare` (src/native/package.json pins
12
+ // "type": "commonjs"), so these become require() calls and the interop is
13
+ // babel's. import.meta is unavailable after that transform, hence no
14
+ // createRequire here.
15
+
16
+ const {
17
+ GUTTER,
18
+ glyph,
19
+ t,
20
+ header,
21
+ duration,
22
+ isInteractive,
23
+ isVerbose,
24
+ step: stepLine
25
+ } = _theme.default;
26
+ const {
27
+ Spinner
28
+ } = _spinner.default;
29
+
30
+ /**
31
+ * Step-by-step progress for the native builds.
32
+ *
33
+ * Append-only by design. An earlier version redrew the whole step tree in
34
+ * place on every change, which meant the renderer had to own a multi-line
35
+ * region of the screen -- and every interleaving with child output (gradle,
36
+ * xcodebuild) became a cursor-math bug. Here each step settles into a single
37
+ * permanent line as it finishes, and the only live thing is a one-line spinner
38
+ * that owns nothing but its own row. Streamed output can therefore appear
39
+ * between steps with nothing to corrupt.
40
+ *
41
+ * The public API (start/complete/fail/log/pause/resume/printTreeContent) is
42
+ * unchanged, because the native build code calls it from ~400 places.
43
+ */
9
44
  class TerminalProgress {
45
+ /**
46
+ * @param {object} steps - id -> description
47
+ * @param {string} title - the invocation, e.g. "catalyst buildApp"
48
+ * @param {object} [options]
49
+ * @param {string} [options.subject] - the target, e.g. "android"
50
+ */
10
51
  constructor(steps, title = "Setup Progress", options = {}) {
11
52
  this.steps = new Map(Object.entries(steps).map(([id, description]) => [id, {
12
53
  id,
13
54
  description,
14
55
  status: "pending",
15
- error: null
56
+ error: null,
57
+ startedAt: 0
16
58
  }]));
17
59
  this.title = title;
60
+ this.subject = options.subject;
61
+ this.startedAt = Date.now();
18
62
  this.currentStep = null;
19
- this.isPaused = false;
20
- this.lineCount = 0;
21
- this.lastRender = "";
22
-
23
- // Default styling options
24
- this.options = {
25
- titlePaddingTop: options.titlePaddingTop ?? 1,
26
- titlePaddingBottom: options.titlePaddingBottom ?? 1,
27
- stepPaddingLeft: options.stepPaddingLeft ?? 2,
28
- stepSpacing: options.stepSpacing ?? 0,
29
- errorPaddingLeft: options.errorPaddingLeft ?? 4,
30
- bottomMargin: options.bottomMargin ?? 1
31
- };
63
+ this.isInteractive = options.isInteractive ?? isInteractive();
64
+ this.spinner = new Spinner(process.stdout);
65
+ this.headerPrinted = false;
66
+ this.pauseDepth = 0;
67
+ // The native build narrates every internal decision ("Package.swift
68
+ // already up to date", "Copied 0 plugin files"). Useful when debugging
69
+ // the build itself, noise otherwise -- so by default those notes ride on
70
+ // the spinner's row and vanish, and --verbose prints them as lines.
71
+ this.verbose = options.verbose ?? isVerbose();
32
72
  }
33
- static icons = {
34
- completed: "✓",
35
- running: "◆",
36
- pending: "○",
37
- error: "✗",
38
- info: "ℹ",
39
- warning: "⚠",
40
- prompt: "?"
41
- };
42
- printTreeContent(title, content) {
43
- // Add top padding
44
- console.log("\n".repeat(this.options.titlePaddingTop));
45
-
46
- // Print the title
47
- console.log(`${_ansis.default.cyan(_ansis.default.bold(title))}`);
48
-
49
- // Process and print each line of content
50
- for (const line of content) {
51
- if (typeof line === "string") {
52
- console.log(`${" ".repeat(this.options.stepPaddingLeft)}${line}`);
53
- } else if (typeof line === "object") {
54
- const {
55
- text,
56
- indent = 0,
57
- prefix = "",
58
- color = "white"
59
- } = line;
60
- const indentation = " ".repeat(this.options.stepPaddingLeft + indent * 3);
61
- const coloredText = _ansis.default[color] ? _ansis.default[color](text) : text;
62
- console.log(`${indentation}${prefix}${coloredText}`);
63
- }
64
- }
73
+ ensureHeader() {
74
+ if (this.headerPrinted) return;
75
+ this.headerPrinted = true;
76
+ process.stdout.write(header(this.title, this.subject));
65
77
  }
66
78
  start(id) {
67
79
  const step = this.steps.get(id);
68
80
  if (!step) throw new Error(`Step ${id} not found`);
81
+ this.ensureHeader();
69
82
  this.currentStep = step;
70
83
  step.status = "running";
71
- this.render();
84
+ step.startedAt = Date.now();
85
+ this.spinner.start(step.description);
72
86
  }
73
87
  complete(id) {
74
88
  const step = this.steps.get(id);
75
89
  if (!step) throw new Error(`Step ${id} not found`);
76
90
  step.status = "completed";
77
- if (this.currentStep === step) {
78
- this.currentStep = null;
79
- }
80
- this.render();
91
+ this.settle(step, "done");
81
92
  }
82
93
  fail(id, error) {
83
94
  const step = this.steps.get(id);
84
95
  if (!step) throw new Error(`Step ${id} not found`);
85
96
  step.status = "error";
86
97
  step.error = error;
87
- if (this.currentStep === step) {
88
- this.currentStep = null;
89
- }
90
- this.render();
91
- }
92
- pause() {
93
- this.isPaused = true;
94
- this.render();
95
- console.log("");
96
- }
97
- resume() {
98
- this.isPaused = false;
99
- console.log("");
100
- this.render();
101
- }
102
- getStepIcon(step) {
103
- const icon = TerminalProgress.icons[step.status] || TerminalProgress.icons.pending;
104
- switch (step.status) {
105
- case "completed":
106
- return _ansis.default.green(icon);
107
- case "error":
108
- return _ansis.default.red(icon);
109
- case "running":
110
- return _ansis.default.blue(icon);
111
- default:
112
- return _ansis.default.gray(icon);
98
+ this.settle(step, "fail");
99
+
100
+ // A bare exit-code message says nothing the ✗ line has not already
101
+ // said, and the diagnostic that follows repeats it verbatim as its
102
+ // headline. Only print detail that adds something.
103
+ if (error && !/^Command failed with exit code \d+$/.test(String(error).trim())) {
104
+ console.log(`${GUTTER}${t.dim(glyph.last)} ${t.bad(error)}`);
113
105
  }
114
106
  }
115
- clearLines() {
116
- if (this.lineCount > 0 && process.stdout.isTTY && process.stdout.moveCursor) {
117
- process.stdout.moveCursor(0, -this.lineCount);
118
- process.stdout.cursorTo(0);
119
- process.stdout.clearScreenDown();
107
+ settle(step, state) {
108
+ this.ensureHeader();
109
+ if (this.currentStep === step) this.currentStep = null;
110
+ if (this.spinner.active) {
111
+ this.spinner.stop(state, step.description);
112
+ } else {
113
+ console.log(stepLine(step.description, step.startedAt ? Date.now() - step.startedAt : null, state));
120
114
  }
121
115
  }
116
+
117
+ /**
118
+ * A note that belongs with the current step rather than being a step.
119
+ *
120
+ * Warnings, errors, prompts and `note` always print, beneath the running
121
+ * line and marked as subordinate. `info` and `success` are narration: by
122
+ * default they replace the spinner's detail text and leave no line behind,
123
+ * and only --verbose keeps them. Use `note` for the rare line a user must
124
+ * act on or keep (a manual launch step, where a keystore was written).
125
+ */
122
126
  log(message, type = "info") {
123
- this.pause();
124
- let icon;
125
- let color;
126
- switch (type) {
127
- case "success":
128
- icon = TerminalProgress.icons.completed;
129
- color = _ansis.default.green;
130
- break;
131
- case "error":
132
- icon = TerminalProgress.icons.error;
133
- color = _ansis.default.red;
134
- break;
135
- case "warning":
136
- icon = TerminalProgress.icons.warning;
137
- color = _ansis.default.yellow;
138
- break;
139
- case "prompt":
140
- icon = TerminalProgress.icons.prompt;
141
- color = _ansis.default.yellow;
142
- break;
143
- default:
144
- icon = TerminalProgress.icons.info;
145
- color = _ansis.default.blue;
127
+ if (!this.verbose && (type === "info" || type === "success")) {
128
+ if (message && this.currentStep) this.status(String(message).trim());
129
+ return;
146
130
  }
147
- console.log(`${" ".repeat(this.options.stepPaddingLeft)}${color(icon)} ${message}`);
148
- this.resume();
131
+ this.ensureHeader();
132
+ const mark = type === "success" ? t.ok(glyph.done) : type === "error" ? t.bad(glyph.fail) : type === "warning" ? t.warn(glyph.warn) : type === "prompt" ? t.warn("?") : type === "note" ? t.accent(glyph.arrow) : t.dim(glyph.info);
133
+
134
+ // Stand the spinner down for exactly one line, then bring it back, so
135
+ // the message lands above the live row instead of fighting it.
136
+ const wasSpinning = this.spinner.active;
137
+ if (wasSpinning) this.spinner.pause();
138
+ console.log(`${GUTTER}${t.dim(glyph.pipe)} ${mark} ${message}`);
139
+ if (wasSpinning) this.spinner.resume();
149
140
  }
150
- render() {
151
- if (this.isPaused) return;
152
- this.clearLines();
153
- let output = "";
154
- let currentLineCount = 0;
155
141
 
156
- // Add top padding
157
- output += "\n".repeat(this.options.titlePaddingTop);
158
- currentLineCount += this.options.titlePaddingTop;
142
+ /** The one action that fixes a failure, under the ✗ line. */
143
+ hint(text) {
144
+ if (!text) return;
145
+ this.ensureHeader();
146
+ if (this.spinner.active) this.spinner.pause();
147
+ console.log(`\n${GUTTER}${t.accent("hint")} ${text}\n`);
148
+ }
159
149
 
160
- // Render title
161
- output += `${_ansis.default.bold(_ansis.default.cyan(this.title))}\n`;
162
- output += `${_ansis.default.gray("─".repeat(this.title.length))}\n`;
163
- currentLineCount += 2;
150
+ /**
151
+ * Stand the renderer down so a child process can write to the terminal.
152
+ * Nestable: only the outermost resume restarts the animation.
153
+ */
154
+ pause() {
155
+ this.pauseDepth++;
156
+ if (this.pauseDepth > 1) return;
157
+ this.spinner.pause();
158
+ }
159
+ resume() {
160
+ if (this.pauseDepth === 0) return;
161
+ this.pauseDepth--;
162
+ if (this.pauseDepth > 0) return;
163
+ this.spinner.resume();
164
+ }
164
165
 
165
- // Add padding after title
166
- output += "\n".repeat(this.options.titlePaddingBottom);
167
- currentLineCount += this.options.titlePaddingBottom;
166
+ /**
167
+ * Show what the running step is doing right now, on the spinner's own row.
168
+ *
169
+ * This replaces streaming a child's output: the row is rewritten, never
170
+ * appended to, so a build that emits hundreds of lines costs one line of
171
+ * screen. Ignored when there is no live spinner, and a null detail leaves
172
+ * the label alone.
173
+ */
174
+ status(detail) {
175
+ if (!detail || !this.currentStep) return;
176
+ this.spinner.update(`${this.currentStep.description} ${t.dim(detail)}`);
177
+ }
168
178
 
169
- // Render steps
170
- for (const step of this.steps.values()) {
171
- const icon = this.getStepIcon(step);
172
- const description = step.status === "error" ? _ansis.default.red(step.description) : step.status === "completed" ? _ansis.default.green(step.description) : step.status === "running" ? _ansis.default.blue(step.description) : _ansis.default.gray(step.description);
173
- const stepPadding = " ".repeat(this.options.stepPaddingLeft);
174
- output += `${stepPadding}${icon} ${description}\n`;
175
- currentLineCount++;
176
- if (step.status === "error" && step.error) {
177
- const errorPadding = " ".repeat(this.options.errorPaddingLeft);
178
- output += `${errorPadding}${_ansis.default.red("↳")} ${_ansis.default.red(step.error)}\n`;
179
- currentLineCount++;
180
- }
179
+ /**
180
+ * The closing line every command ends on: what happened, how long it took,
181
+ * and the one command to run next.
182
+ */
183
+ summary(result, nextStep) {
184
+ this.ensureHeader();
185
+ const elapsed = duration(Date.now() - this.startedAt);
186
+ console.log(`${GUTTER}${t.ok(glyph.done)} ${t.bold(`${result} in ${elapsed}`)}`);
187
+ if (nextStep) {
188
+ // Name exactly one command in the accent. Colour each fragment
189
+ // separately -- wrapping dim() around text that already contains a
190
+ // reset would end the dim early and leave the tail uncoloured.
191
+ const parts = nextStep.split(/(catalyst [\w:]+)/);
192
+ const line = parts.map((part, index) => index % 2 === 1 ? t.accent(part) : t.dim(part)).join("");
193
+ console.log(`${GUTTER} ${line}`);
194
+ }
195
+ console.log("");
196
+ }
181
197
 
182
- // Add spacing between steps
183
- if (this.options.stepSpacing > 0) {
184
- output += "\n".repeat(this.options.stepSpacing);
185
- currentLineCount += this.options.stepSpacing;
198
+ /** A titled block of key/value detail, used for build summaries. */
199
+ printTreeContent(title, content) {
200
+ this.ensureHeader();
201
+ // A block is a settled thing; the spinner must be off its row first, or
202
+ // the live line eats the blank separator above the title.
203
+ this.spinner.stop();
204
+ console.log(`\n${GUTTER}${t.bold(title)}`);
205
+ for (const [index, line] of content.entries()) {
206
+ const isLast = index === content.length - 1;
207
+ if (typeof line === "string") {
208
+ // A bare string is a section label rather than a leaf.
209
+ console.log(line.startsWith("\n") ? `${GUTTER}${t.dim(line.trim())}` : `${GUTTER}${line}`);
210
+ continue;
186
211
  }
212
+ const {
213
+ text,
214
+ color = "white"
215
+ } = line;
216
+ const paint = color === "white" ? s => s : t[colorAlias(color)] || (s => s);
217
+ console.log(`${GUTTER}${t.dim(isLast ? glyph.last : glyph.branch)} ${paint(text)}`);
187
218
  }
219
+ console.log("");
220
+ }
221
+ }
188
222
 
189
- // Add bottom margin
190
- output += "\n".repeat(this.options.bottomMargin);
191
- currentLineCount += this.options.bottomMargin;
192
- if (output !== this.lastRender) {
193
- process.stdout.write(output);
194
- this.lastRender = output;
195
- this.lineCount = currentLineCount;
196
- }
223
+ /** Map the legacy colour names used across the native build to the theme. */
224
+ function colorAlias(name) {
225
+ switch (name) {
226
+ case "green":
227
+ return "ok";
228
+ case "red":
229
+ return "bad";
230
+ case "yellow":
231
+ return "warn";
232
+ case "cyan":
233
+ return "accent";
234
+ default:
235
+ return "dim";
197
236
  }
198
237
  }
199
238
  var _default = exports.default = TerminalProgress;