@gtsx/core 0.0.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/LICENSE +21 -0
- package/dist/analyzer.d.ts +31 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +397 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/boundary-rect.d.ts +3 -0
- package/dist/boundary-rect.d.ts.map +1 -0
- package/dist/boundary-rect.js +70 -0
- package/dist/boundary-rect.js.map +1 -0
- package/dist/browser-capture.d.ts +8 -0
- package/dist/browser-capture.d.ts.map +1 -0
- package/dist/browser-capture.js +47 -0
- package/dist/browser-capture.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +666 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-model.d.ts +6 -0
- package/dist/config-model.d.ts.map +1 -0
- package/dist/config-model.js +25 -0
- package/dist/config-model.js.map +1 -0
- package/dist/config-types.d.ts +32 -0
- package/dist/config-types.d.ts.map +1 -0
- package/dist/config-types.js +2 -0
- package/dist/config-types.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/define-config.d.ts +3 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +4 -0
- package/dist/define-config.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +84 -0
- package/dist/init.js.map +1 -0
- package/dist/preview-protocol.d.ts +78 -0
- package/dist/preview-protocol.d.ts.map +1 -0
- package/dist/preview-protocol.js +95 -0
- package/dist/preview-protocol.js.map +1 -0
- package/dist/project-index.d.ts +35 -0
- package/dist/project-index.d.ts.map +1 -0
- package/dist/project-index.js +289 -0
- package/dist/project-index.js.map +1 -0
- package/dist/project-scope.d.ts +8 -0
- package/dist/project-scope.d.ts.map +1 -0
- package/dist/project-scope.js +56 -0
- package/dist/project-scope.js.map +1 -0
- package/dist/react-transform.d.ts +15 -0
- package/dist/react-transform.d.ts.map +1 -0
- package/dist/react-transform.js +138 -0
- package/dist/react-transform.js.map +1 -0
- package/dist/runtime-values.d.ts +73 -0
- package/dist/runtime-values.d.ts.map +1 -0
- package/dist/runtime-values.js +124 -0
- package/dist/runtime-values.js.map +1 -0
- package/dist/runtime.d.ts +44 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +218 -0
- package/dist/runtime.js.map +1 -0
- package/dist/script-adapter.d.ts +22 -0
- package/dist/script-adapter.d.ts.map +1 -0
- package/dist/script-adapter.js +80 -0
- package/dist/script-adapter.js.map +1 -0
- package/dist/studio-client.d.ts +112 -0
- package/dist/studio-client.d.ts.map +1 -0
- package/dist/studio-client.js +1342 -0
- package/dist/studio-client.js.map +1 -0
- package/dist/studio-manifest-model.d.ts +41 -0
- package/dist/studio-manifest-model.d.ts.map +1 -0
- package/dist/studio-manifest-model.js +24 -0
- package/dist/studio-manifest-model.js.map +1 -0
- package/dist/studio-manifest.d.ts +23 -0
- package/dist/studio-manifest.d.ts.map +1 -0
- package/dist/studio-manifest.js +57 -0
- package/dist/studio-manifest.js.map +1 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +84 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { analyzeEntry } from "./analyzer.js";
|
|
7
|
+
import { capturePreviewPage } from "./browser-capture.js";
|
|
8
|
+
import { loadGTSXConfig } from "./config.js";
|
|
9
|
+
import { initGTSX } from "./init.js";
|
|
10
|
+
import { buildGTSXProjectIndex } from "./project-index.js";
|
|
11
|
+
import { discoverGTSXProgramFiles, findNearestTSConfig } from "./project-scope.js";
|
|
12
|
+
import { expandCommand, runScriptAdapter } from "./script-adapter.js";
|
|
13
|
+
const HELP = `gtsx
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
gtsx init [--dry-run]
|
|
17
|
+
gtsx check [-p <tsconfig-or-dir>] <entry.g.tsx[#export]|dir> [--json]
|
|
18
|
+
gtsx serve [-p <tsconfig-or-dir>] [--port <port>]
|
|
19
|
+
gtsx capture [-p <tsconfig-or-dir>] <entry.g.tsx[#export]|dir> [--case <name>|--all] [--gcase <entry.g.tsx#export:case>] [--viewport 1440x900] [--out <file.png|dir>] [--port <port>]
|
|
20
|
+
gtsx strip [--check]
|
|
21
|
+
gtsx diagnose
|
|
22
|
+
`;
|
|
23
|
+
export async function runCLI(args, context) {
|
|
24
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
25
|
+
return { exitCode: 0, stdout: HELP, stderr: context.stderr };
|
|
26
|
+
}
|
|
27
|
+
const projectSelection = resolveProjectSelection(args, context.cwd);
|
|
28
|
+
if (projectSelection.diagnostics.length > 0) {
|
|
29
|
+
return diagnosticsResult(projectSelection.diagnostics);
|
|
30
|
+
}
|
|
31
|
+
args = projectSelection.args;
|
|
32
|
+
const cwd = projectSelection.cwd;
|
|
33
|
+
if (args[0] === "init") {
|
|
34
|
+
return initGTSX({
|
|
35
|
+
cwd,
|
|
36
|
+
dryRun: args.includes("--dry-run"),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (args[0] === "check") {
|
|
40
|
+
const entry = args[1];
|
|
41
|
+
if (!entry) {
|
|
42
|
+
return { exitCode: 1, stdout: context.stdout, stderr: "Missing entry for gtsx check.\n" };
|
|
43
|
+
}
|
|
44
|
+
if (isDirectory(cwd, entry)) {
|
|
45
|
+
if (args.includes("--json")) {
|
|
46
|
+
return { exitCode: 1, stdout: context.stdout, stderr: "Directory JSON output is not supported yet.\n" };
|
|
47
|
+
}
|
|
48
|
+
return checkResolvedEntries(cwd, discoverGTSXEntryCoordinates(cwd, entry, projectSelection.tsconfigPath), {
|
|
49
|
+
fallbackFile: entry,
|
|
50
|
+
json: false,
|
|
51
|
+
stderr: context.stderr,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (projectSelection.tsconfigPath && !isEntryInGTSXScope(cwd, entry, projectSelection.tsconfigPath)) {
|
|
55
|
+
return entryOutsideProjectScopeResult(entry);
|
|
56
|
+
}
|
|
57
|
+
return checkResolvedEntries(cwd, resolveGTSXEntryCoordinates(cwd, entry, projectSelection.tsconfigPath), {
|
|
58
|
+
fallbackFile: entry,
|
|
59
|
+
json: args.includes("--json"),
|
|
60
|
+
stderr: context.stderr,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (args[0] === "serve") {
|
|
64
|
+
const config = loadGTSXConfig(cwd);
|
|
65
|
+
if (!config.config)
|
|
66
|
+
return diagnosticsResult(config.diagnostics);
|
|
67
|
+
if (!config.config.preview.studioUrl) {
|
|
68
|
+
return diagnosticsResult([
|
|
69
|
+
{
|
|
70
|
+
stage: "adapter-configuration",
|
|
71
|
+
code: "missing-studio-url",
|
|
72
|
+
message: "Add preview.studioUrl to gtsx.config.ts after integrating the /gtsx/studio route.",
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
const port = readOption(args, "--port") ?? "4300";
|
|
77
|
+
const studioUrl = expandUrl(config.config.preview.studioUrl, { entry: "", caseName: "", port });
|
|
78
|
+
const previewServer = await startPreviewServer(config.config.preview.serve, cwd, { port, readyUrl: studioUrl });
|
|
79
|
+
if (previewServer.exitCode !== 0)
|
|
80
|
+
return previewServer;
|
|
81
|
+
return {
|
|
82
|
+
exitCode: 0,
|
|
83
|
+
stdout: `Studio: ${studioUrl}\n`,
|
|
84
|
+
stderr: previewServer.stderr,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (args[0] === "capture") {
|
|
88
|
+
const entry = args[1];
|
|
89
|
+
if (!entry)
|
|
90
|
+
return { exitCode: 1, stdout: context.stdout, stderr: "Missing entry for gtsx capture.\n" };
|
|
91
|
+
if (isDirectory(cwd, entry)) {
|
|
92
|
+
if (!args.includes("--all")) {
|
|
93
|
+
return diagnosticsResult([
|
|
94
|
+
{
|
|
95
|
+
stage: "browser-capture",
|
|
96
|
+
code: "directory-capture-requires-all",
|
|
97
|
+
message: "Directory capture requires --all so each entry can render a contact sheet.",
|
|
98
|
+
file: entry,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
const resolvedEntries = discoverGTSXEntryCoordinates(cwd, entry, projectSelection.tsconfigPath);
|
|
103
|
+
if (resolvedEntries.entries.length === 0) {
|
|
104
|
+
return diagnosticsResult(nonEmptyDiagnostics(resolvedEntries.diagnostics, entry));
|
|
105
|
+
}
|
|
106
|
+
const checks = resolvedEntries.entries.map((candidate) => analyzeEntry({ cwd, entry: candidate }));
|
|
107
|
+
if (resolvedEntries.diagnostics.length > 0 || checks.some((check) => check.diagnostics.length > 0)) {
|
|
108
|
+
return {
|
|
109
|
+
exitCode: 1,
|
|
110
|
+
stdout: [checks.map(formatCheckResult).join("\n"), formatDiagnostics(resolvedEntries.diagnostics)].join(""),
|
|
111
|
+
stderr: context.stderr,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const config = loadGTSXConfig(cwd);
|
|
115
|
+
if (!config.config)
|
|
116
|
+
return diagnosticsResult(config.diagnostics);
|
|
117
|
+
if (!config.config.preview.allUrl) {
|
|
118
|
+
return diagnosticsResult([
|
|
119
|
+
{
|
|
120
|
+
stage: "adapter-configuration",
|
|
121
|
+
code: "missing-preview-all-url",
|
|
122
|
+
message: "Missing preview.allUrl in gtsx.config.ts for contact sheet capture.",
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
const out = readOption(args, "--out") ?? "gtsx-captures";
|
|
127
|
+
if (out.endsWith(".png")) {
|
|
128
|
+
return diagnosticsResult([
|
|
129
|
+
{
|
|
130
|
+
stage: "browser-capture",
|
|
131
|
+
code: "directory-output-must-be-directory",
|
|
132
|
+
message: "Directory capture writes one PNG per entry, so --out must be a directory.",
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
const port = readOption(args, "--port") ?? "4300";
|
|
137
|
+
const viewport = readOption(args, "--viewport") ?? "1440x900";
|
|
138
|
+
const gcases = readOptions(args, "--gcase");
|
|
139
|
+
const readyUrl = expandUrl(config.config.preview.allUrl, {
|
|
140
|
+
entry: resolvedEntries.entries[0] ?? "",
|
|
141
|
+
caseName: "",
|
|
142
|
+
port,
|
|
143
|
+
gcases,
|
|
144
|
+
});
|
|
145
|
+
const previewServer = await startPreviewServer(config.config.preview.serve, cwd, {
|
|
146
|
+
port,
|
|
147
|
+
readyUrl,
|
|
148
|
+
detached: true,
|
|
149
|
+
});
|
|
150
|
+
if (previewServer.exitCode !== 0)
|
|
151
|
+
return previewServer;
|
|
152
|
+
try {
|
|
153
|
+
const outputs = [];
|
|
154
|
+
for (const candidate of resolvedEntries.entries) {
|
|
155
|
+
const outPath = outForDirectoryContactSheet(out, candidate);
|
|
156
|
+
await capturePreviewPage({
|
|
157
|
+
cwd,
|
|
158
|
+
url: expandUrl(config.config.preview.allUrl, { entry: candidate, caseName: "", port, gcases }),
|
|
159
|
+
viewport,
|
|
160
|
+
out: outPath,
|
|
161
|
+
});
|
|
162
|
+
outputs.push(`Captured ${candidate} contact sheet to ${outPath}\n`);
|
|
163
|
+
}
|
|
164
|
+
return { exitCode: 0, stdout: outputs.join(""), stderr: context.stderr };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return diagnosticsResult([
|
|
168
|
+
{
|
|
169
|
+
stage: "browser-capture",
|
|
170
|
+
code: "browser-capture-failed",
|
|
171
|
+
message: error instanceof Error ? error.message : String(error),
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
previewServer.stop();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (projectSelection.tsconfigPath && !isEntryInGTSXScope(cwd, entry, projectSelection.tsconfigPath)) {
|
|
180
|
+
return entryOutsideProjectScopeResult(entry);
|
|
181
|
+
}
|
|
182
|
+
const resolvedEntry = resolveGTSXEntryCoordinates(cwd, entry, projectSelection.tsconfigPath);
|
|
183
|
+
if (resolvedEntry.entries.length === 0) {
|
|
184
|
+
return diagnosticsResult(nonEmptyDiagnostics(resolvedEntry.diagnostics, entry));
|
|
185
|
+
}
|
|
186
|
+
if (resolvedEntry.entries.length > 1) {
|
|
187
|
+
return diagnosticsResult([
|
|
188
|
+
{
|
|
189
|
+
stage: "contract-extraction",
|
|
190
|
+
code: "ambiguous-entry-coordinate",
|
|
191
|
+
message: `${entry} contains multiple GTSX component exports; pass one explicit coordinate such as ${resolvedEntry.entries[0]}.`,
|
|
192
|
+
file: entry,
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
}
|
|
196
|
+
const selectedEntry = resolvedEntry.entries[0] ?? entry;
|
|
197
|
+
const check = analyzeEntry({ cwd, entry: selectedEntry });
|
|
198
|
+
if (check.diagnostics.length > 0) {
|
|
199
|
+
return { exitCode: 1, stdout: formatCheckResult(check), stderr: context.stderr };
|
|
200
|
+
}
|
|
201
|
+
const config = loadGTSXConfig(cwd);
|
|
202
|
+
if (!config.config)
|
|
203
|
+
return diagnosticsResult(config.diagnostics);
|
|
204
|
+
const port = readOption(args, "--port") ?? "4300";
|
|
205
|
+
const viewport = readOption(args, "--viewport") ?? "1440x900";
|
|
206
|
+
const out = readOption(args, "--out") ?? "gtsx-capture.png";
|
|
207
|
+
const captureAllCases = args.includes("--all");
|
|
208
|
+
const selectedCase = readOption(args, "--case") ?? check.cases[0]?.name;
|
|
209
|
+
const gcases = readOptions(args, "--gcase");
|
|
210
|
+
if (captureAllCases && !config.config.preview.allUrl) {
|
|
211
|
+
return diagnosticsResult([
|
|
212
|
+
{
|
|
213
|
+
stage: "adapter-configuration",
|
|
214
|
+
code: "missing-preview-all-url",
|
|
215
|
+
message: "Missing preview.allUrl in gtsx.config.ts for contact sheet capture.",
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
if (!captureAllCases && !config.config.preview.url) {
|
|
220
|
+
return diagnosticsResult([
|
|
221
|
+
{
|
|
222
|
+
stage: "adapter-configuration",
|
|
223
|
+
code: "missing-preview-url",
|
|
224
|
+
message: "Missing preview.url in gtsx.config.ts for browser capture.",
|
|
225
|
+
},
|
|
226
|
+
]);
|
|
227
|
+
}
|
|
228
|
+
if (!captureAllCases && !selectedCase) {
|
|
229
|
+
return diagnosticsResult([
|
|
230
|
+
{
|
|
231
|
+
stage: "contract-extraction",
|
|
232
|
+
code: "missing-cases",
|
|
233
|
+
message: `No cases found for ${entry}.`,
|
|
234
|
+
file: entry,
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
const captureUrl = captureAllCases
|
|
239
|
+
? expandUrl(config.config.preview.allUrl ?? "", { entry: selectedEntry, caseName: "", port, gcases })
|
|
240
|
+
: expandUrl(config.config.preview.url ?? "", { entry: selectedEntry, caseName: selectedCase ?? "", port, gcases });
|
|
241
|
+
const previewServer = await startPreviewServer(config.config.preview.serve, cwd, {
|
|
242
|
+
port,
|
|
243
|
+
readyUrl: captureUrl,
|
|
244
|
+
detached: true,
|
|
245
|
+
});
|
|
246
|
+
if (previewServer.exitCode !== 0)
|
|
247
|
+
return previewServer;
|
|
248
|
+
try {
|
|
249
|
+
if (captureAllCases) {
|
|
250
|
+
const outPath = outForEntryContactSheet(out, selectedEntry);
|
|
251
|
+
await capturePreviewPage({
|
|
252
|
+
cwd,
|
|
253
|
+
url: captureUrl,
|
|
254
|
+
viewport,
|
|
255
|
+
out: outPath,
|
|
256
|
+
});
|
|
257
|
+
return { exitCode: 0, stdout: `Captured ${selectedEntry} contact sheet to ${outPath}\n`, stderr: context.stderr };
|
|
258
|
+
}
|
|
259
|
+
await capturePreviewPage({
|
|
260
|
+
cwd,
|
|
261
|
+
url: captureUrl,
|
|
262
|
+
viewport,
|
|
263
|
+
out,
|
|
264
|
+
});
|
|
265
|
+
return { exitCode: 0, stdout: `Captured ${selectedCase} to ${out}\n`, stderr: context.stderr };
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return diagnosticsResult([
|
|
269
|
+
{
|
|
270
|
+
stage: "browser-capture",
|
|
271
|
+
code: "browser-capture-failed",
|
|
272
|
+
message: error instanceof Error ? error.message : String(error),
|
|
273
|
+
},
|
|
274
|
+
]);
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
previewServer.stop();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (args[0] === "strip") {
|
|
281
|
+
const config = loadGTSXConfig(cwd);
|
|
282
|
+
if (!config.config)
|
|
283
|
+
return diagnosticsResult(config.diagnostics);
|
|
284
|
+
const adapter = await runScriptAdapter(config.config, "strip", {
|
|
285
|
+
cwd,
|
|
286
|
+
check: args.includes("--check"),
|
|
287
|
+
});
|
|
288
|
+
return adapterResult(adapter);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
exitCode: 1,
|
|
292
|
+
stdout: context.stdout,
|
|
293
|
+
stderr: `Unknown command: ${args[0] ?? ""}\n`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function resolveProjectSelection(args, cwd) {
|
|
297
|
+
const projectOptionIndex = args.findIndex((arg) => arg === "-p" || arg === "--project");
|
|
298
|
+
if (projectOptionIndex < 0) {
|
|
299
|
+
const configProjectTSConfig = resolveConfiguredTSConfig(cwd);
|
|
300
|
+
if (configProjectTSConfig) {
|
|
301
|
+
return {
|
|
302
|
+
args,
|
|
303
|
+
cwd: dirname(configProjectTSConfig),
|
|
304
|
+
tsconfigPath: configProjectTSConfig,
|
|
305
|
+
diagnostics: [],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const tsconfigPath = findNearestTSConfig(cwd);
|
|
309
|
+
if (!tsconfigPath) {
|
|
310
|
+
return { args, cwd, diagnostics: [] };
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
args,
|
|
314
|
+
cwd: dirname(tsconfigPath),
|
|
315
|
+
tsconfigPath,
|
|
316
|
+
diagnostics: [],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const projectValue = args[projectOptionIndex + 1];
|
|
320
|
+
if (!projectValue) {
|
|
321
|
+
return {
|
|
322
|
+
args,
|
|
323
|
+
cwd,
|
|
324
|
+
diagnostics: [
|
|
325
|
+
{
|
|
326
|
+
stage: "typescript",
|
|
327
|
+
code: "missing-project-option-value",
|
|
328
|
+
message: "Missing value for -p/--project.",
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const nextArgs = [...args.slice(0, projectOptionIndex), ...args.slice(projectOptionIndex + 2)];
|
|
334
|
+
const projectPath = resolve(cwd, projectValue);
|
|
335
|
+
const projectStat = statOrUndefined(projectPath);
|
|
336
|
+
const tsconfigPath = projectStat?.isDirectory() ? findNearestTSConfig(projectPath) : projectPath;
|
|
337
|
+
if (!tsconfigPath || !statOrUndefined(tsconfigPath)?.isFile()) {
|
|
338
|
+
return {
|
|
339
|
+
args: nextArgs,
|
|
340
|
+
cwd,
|
|
341
|
+
diagnostics: [
|
|
342
|
+
{
|
|
343
|
+
stage: "typescript",
|
|
344
|
+
code: "missing-tsconfig",
|
|
345
|
+
message: `Could not resolve a TypeScript project from ${projectValue}.`,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
args: nextArgs,
|
|
352
|
+
cwd: dirname(tsconfigPath),
|
|
353
|
+
tsconfigPath,
|
|
354
|
+
diagnostics: [],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function resolveConfiguredTSConfig(cwd) {
|
|
358
|
+
const config = loadGTSXConfig(cwd);
|
|
359
|
+
if (!config.config?.project?.tsconfig)
|
|
360
|
+
return undefined;
|
|
361
|
+
const tsconfigPath = resolve(cwd, config.config.project.tsconfig);
|
|
362
|
+
return statOrUndefined(tsconfigPath)?.isFile() ? tsconfigPath : undefined;
|
|
363
|
+
}
|
|
364
|
+
function isDirectory(cwd, target) {
|
|
365
|
+
try {
|
|
366
|
+
return statSync(resolve(cwd, target)).isDirectory();
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function statOrUndefined(path) {
|
|
373
|
+
try {
|
|
374
|
+
return statSync(path);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function resolveGTSXEntryCoordinates(cwd, entry, tsconfigPath) {
|
|
381
|
+
if (hasExplicitExportCoordinate(entry)) {
|
|
382
|
+
return { entries: [entry], diagnostics: [] };
|
|
383
|
+
}
|
|
384
|
+
return discoverGTSXFileEntryCoordinates(cwd, entry, tsconfigPath);
|
|
385
|
+
}
|
|
386
|
+
function discoverGTSXEntryCoordinates(cwd, targetDirectory, tsconfigPath) {
|
|
387
|
+
const index = buildGTSXProjectIndex({ cwd, projectRoot: targetDirectory, tsconfigPath });
|
|
388
|
+
return {
|
|
389
|
+
entries: index.files.flatMap((file) => file.components.map((component) => component.coordinate)),
|
|
390
|
+
diagnostics: index.files.filter((file) => file.components.length === 0).flatMap((file) => file.diagnostics),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function discoverGTSXFileEntryCoordinates(cwd, entry, tsconfigPath) {
|
|
394
|
+
const file = normalizeProjectPath(entryFile(entry));
|
|
395
|
+
const projectRoot = dirname(file);
|
|
396
|
+
const index = buildGTSXProjectIndex({
|
|
397
|
+
cwd,
|
|
398
|
+
projectRoot: projectRoot === "." ? "." : projectRoot,
|
|
399
|
+
tsconfigPath,
|
|
400
|
+
});
|
|
401
|
+
const indexedFile = index.files.find((candidate) => candidate.path === file);
|
|
402
|
+
if (!indexedFile) {
|
|
403
|
+
return {
|
|
404
|
+
entries: [],
|
|
405
|
+
diagnostics: [
|
|
406
|
+
{
|
|
407
|
+
stage: "contract-extraction",
|
|
408
|
+
code: "entry-not-found",
|
|
409
|
+
message: `GTSX entry does not exist: ${entry}.`,
|
|
410
|
+
file: entry,
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
entries: indexedFile.components.map((component) => component.coordinate),
|
|
417
|
+
diagnostics: indexedFile.components.length === 0 ? indexedFile.diagnostics : [],
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function hasExplicitExportCoordinate(entry) {
|
|
421
|
+
return entry.includes("#");
|
|
422
|
+
}
|
|
423
|
+
function normalizeProjectPath(filePath) {
|
|
424
|
+
return filePath.split(sep).join("/");
|
|
425
|
+
}
|
|
426
|
+
function isEntryInGTSXScope(cwd, entry, tsconfigPath) {
|
|
427
|
+
const file = entryFile(entry);
|
|
428
|
+
return discoverGTSXProgramFiles({ cwd, root: ".", tsconfigPath }).includes(file);
|
|
429
|
+
}
|
|
430
|
+
function entryFile(entry) {
|
|
431
|
+
return entry.split("#", 1)[0] ?? entry;
|
|
432
|
+
}
|
|
433
|
+
function entryOutsideProjectScopeResult(entry) {
|
|
434
|
+
return diagnosticsResult([
|
|
435
|
+
{
|
|
436
|
+
stage: "typescript",
|
|
437
|
+
code: "entry-outside-project-scope",
|
|
438
|
+
message: `${entryFile(entry)} is not in the selected TypeScript project scope.`,
|
|
439
|
+
file: entryFile(entry),
|
|
440
|
+
},
|
|
441
|
+
]);
|
|
442
|
+
}
|
|
443
|
+
function checkResolvedEntries(cwd, resolution, options) {
|
|
444
|
+
if (resolution.entries.length === 0) {
|
|
445
|
+
return diagnosticsResult(nonEmptyDiagnostics(resolution.diagnostics, options.fallbackFile));
|
|
446
|
+
}
|
|
447
|
+
const results = resolution.entries.map((candidate) => analyzeEntry({ cwd, entry: candidate }));
|
|
448
|
+
const diagnostics = [...resolution.diagnostics, ...results.flatMap((result) => result.diagnostics)];
|
|
449
|
+
if (options.json) {
|
|
450
|
+
const stdout = results.length === 1 && resolution.diagnostics.length === 0
|
|
451
|
+
? `${JSON.stringify(results[0], null, 2)}\n`
|
|
452
|
+
: `${JSON.stringify({ entries: results, diagnostics }, null, 2)}\n`;
|
|
453
|
+
return {
|
|
454
|
+
exitCode: diagnostics.length === 0 ? 0 : 1,
|
|
455
|
+
stdout,
|
|
456
|
+
stderr: options.stderr,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
exitCode: diagnostics.length === 0 ? 0 : 1,
|
|
461
|
+
stdout: [results.map(formatCheckResult).join("\n"), formatDiagnostics(resolution.diagnostics)].join(""),
|
|
462
|
+
stderr: options.stderr,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function nonEmptyDiagnostics(diagnostics, target) {
|
|
466
|
+
if (diagnostics.length > 0)
|
|
467
|
+
return diagnostics;
|
|
468
|
+
return [
|
|
469
|
+
{
|
|
470
|
+
stage: "contract-extraction",
|
|
471
|
+
code: "no-entries-found",
|
|
472
|
+
message: `No .g.tsx entries found under ${target}.`,
|
|
473
|
+
file: target,
|
|
474
|
+
},
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
function readOption(args, optionName) {
|
|
478
|
+
const index = args.indexOf(optionName);
|
|
479
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
480
|
+
}
|
|
481
|
+
function readOptions(args, optionName) {
|
|
482
|
+
const values = [];
|
|
483
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
484
|
+
if (args[index] === optionName && args[index + 1]) {
|
|
485
|
+
values.push(args[index + 1]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return values;
|
|
489
|
+
}
|
|
490
|
+
function outForEntryContactSheet(out, entry) {
|
|
491
|
+
if (out.endsWith(".png"))
|
|
492
|
+
return out;
|
|
493
|
+
const fileName = outputPathForEntry(entry).split(/[\\/]/).pop() ?? "gtsx-capture.png";
|
|
494
|
+
return join(out, fileName);
|
|
495
|
+
}
|
|
496
|
+
function outForDirectoryContactSheet(out, entry) {
|
|
497
|
+
return join(out, outputPathForEntry(entry));
|
|
498
|
+
}
|
|
499
|
+
function outputPathForEntry(entry) {
|
|
500
|
+
const coordinate = parseEntryCoordinate(entry);
|
|
501
|
+
const suffix = coordinate.exportName === "default" ? ".png" : `.${sanitizeFilePathSegment(coordinate.exportName)}.png`;
|
|
502
|
+
return coordinate.file.replace(/\.g\.tsx$/, suffix);
|
|
503
|
+
}
|
|
504
|
+
function parseEntryCoordinate(entry) {
|
|
505
|
+
const [file, exportName] = entry.split("#", 2);
|
|
506
|
+
return { file, exportName: exportName || "default" };
|
|
507
|
+
}
|
|
508
|
+
function sanitizeFilePathSegment(value) {
|
|
509
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
510
|
+
}
|
|
511
|
+
async function startPreviewServer(serveCommand, cwd, params) {
|
|
512
|
+
if (!serveCommand) {
|
|
513
|
+
return {
|
|
514
|
+
exitCode: 1,
|
|
515
|
+
stdout: "",
|
|
516
|
+
stderr: "[adapter-configuration] missing-serve-script: Missing preview.serve in gtsx.config.ts.\n",
|
|
517
|
+
stop() { },
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
const child = spawn(expandCommand(serveCommand, { cwd, port: params.port }), {
|
|
521
|
+
cwd,
|
|
522
|
+
detached: Boolean(params.detached && process.platform !== "win32"),
|
|
523
|
+
shell: true,
|
|
524
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
525
|
+
});
|
|
526
|
+
let stdout = "";
|
|
527
|
+
let stderr = "";
|
|
528
|
+
let exitCode;
|
|
529
|
+
child.stdout.on("data", (chunk) => {
|
|
530
|
+
stdout += String(chunk);
|
|
531
|
+
});
|
|
532
|
+
child.stderr.on("data", (chunk) => {
|
|
533
|
+
stderr += String(chunk);
|
|
534
|
+
});
|
|
535
|
+
const stop = () => {
|
|
536
|
+
if (exitCode !== undefined)
|
|
537
|
+
return;
|
|
538
|
+
if (!params.detached || process.platform === "win32") {
|
|
539
|
+
child.kill();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
if (child.pid === undefined)
|
|
544
|
+
throw new Error("Preview server pid is unavailable");
|
|
545
|
+
process.kill(-child.pid, "SIGTERM");
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
child.kill();
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
const exitPromise = new Promise((resolve) => {
|
|
552
|
+
child.on("exit", (code) => {
|
|
553
|
+
exitCode = code ?? 0;
|
|
554
|
+
resolve(exitCode);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
if (params.readyUrl) {
|
|
558
|
+
const ready = await waitForPreviewUrl(params.readyUrl, exitPromise);
|
|
559
|
+
if (ready === "ready") {
|
|
560
|
+
return {
|
|
561
|
+
exitCode: 0,
|
|
562
|
+
stdout,
|
|
563
|
+
stderr,
|
|
564
|
+
stop,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
stop();
|
|
568
|
+
return {
|
|
569
|
+
exitCode: exitCode && exitCode !== 0 ? exitCode : 1,
|
|
570
|
+
stdout,
|
|
571
|
+
stderr: stderr ||
|
|
572
|
+
`[adapter-configuration] preview-server-not-ready: Preview server did not make ${params.readyUrl} reachable before ${ready}.\n`,
|
|
573
|
+
stop() { },
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
await Promise.race([exitPromise, new Promise((resolve) => setTimeout(resolve, 500))]);
|
|
577
|
+
return {
|
|
578
|
+
exitCode: exitCode && exitCode !== 0 ? exitCode : 0,
|
|
579
|
+
stdout,
|
|
580
|
+
stderr,
|
|
581
|
+
stop,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
async function waitForPreviewUrl(readyUrl, exitPromise) {
|
|
585
|
+
const deadline = Date.now() + 10_000;
|
|
586
|
+
while (Date.now() < deadline) {
|
|
587
|
+
const result = await Promise.race([
|
|
588
|
+
exitPromise.then(() => "exit"),
|
|
589
|
+
fetch(readyUrl, { redirect: "manual", signal: AbortSignal.timeout(500) })
|
|
590
|
+
.then((response) => (response.status >= 200 && response.status < 400 ? "ready" : "retry"))
|
|
591
|
+
.catch(() => "retry"),
|
|
592
|
+
]);
|
|
593
|
+
if (result === "ready" || result === "exit")
|
|
594
|
+
return result;
|
|
595
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
596
|
+
}
|
|
597
|
+
return "timeout";
|
|
598
|
+
}
|
|
599
|
+
export function expandUrl(template, params) {
|
|
600
|
+
const replacements = {
|
|
601
|
+
entry: params.entry,
|
|
602
|
+
case: params.caseName,
|
|
603
|
+
port: params.port,
|
|
604
|
+
gcase: params.gcases?.map((gcase) => `&gcase=${encodeURIComponent(gcase)}`).join("") ?? "",
|
|
605
|
+
};
|
|
606
|
+
return template.replace(/\{([a-z]+)\}/g, (_match, key) => {
|
|
607
|
+
if (key === "gcase")
|
|
608
|
+
return replacements.gcase;
|
|
609
|
+
return encodeURIComponent(replacements[key] ?? "");
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
function diagnosticsResult(diagnostics) {
|
|
613
|
+
return {
|
|
614
|
+
exitCode: diagnostics.some((diagnostic) => diagnostic.code.startsWith("missing-strip-script")) ? 0 : 1,
|
|
615
|
+
stdout: formatDiagnostics(diagnostics),
|
|
616
|
+
stderr: "",
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function formatDiagnostics(diagnostics) {
|
|
620
|
+
if (diagnostics.length === 0)
|
|
621
|
+
return "";
|
|
622
|
+
return diagnostics.map((diagnostic) => `[${diagnostic.stage}] ${diagnostic.code}: ${diagnostic.message}`).join("\n") + "\n";
|
|
623
|
+
}
|
|
624
|
+
function adapterResult(adapter) {
|
|
625
|
+
if (adapter.diagnostics.length > 0)
|
|
626
|
+
return diagnosticsResult(adapter.diagnostics);
|
|
627
|
+
return {
|
|
628
|
+
exitCode: adapter.exitCode,
|
|
629
|
+
stdout: adapter.stdout,
|
|
630
|
+
stderr: adapter.stderr,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function formatCheckResult(result) {
|
|
634
|
+
const lines = [`GTSX ${result.mode} entry: ${result.entry}`];
|
|
635
|
+
for (const testCase of result.cases) {
|
|
636
|
+
lines.push(`- ${testCase.name}`);
|
|
637
|
+
}
|
|
638
|
+
for (const diagnostic of result.diagnostics) {
|
|
639
|
+
lines.push(`[${diagnostic.stage}] ${diagnostic.code}: ${diagnostic.message}`);
|
|
640
|
+
}
|
|
641
|
+
return `${lines.join("\n")}\n`;
|
|
642
|
+
}
|
|
643
|
+
function isCLIEntrypoint(moduleUrl, argvPath) {
|
|
644
|
+
if (!argvPath)
|
|
645
|
+
return false;
|
|
646
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
647
|
+
try {
|
|
648
|
+
return realpathSync(modulePath) === realpathSync(argvPath);
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return modulePath === resolve(argvPath);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (isCLIEntrypoint(import.meta.url, process.argv[1])) {
|
|
655
|
+
const result = await runCLI(process.argv.slice(2), {
|
|
656
|
+
cwd: process.cwd(),
|
|
657
|
+
stdout: "",
|
|
658
|
+
stderr: "",
|
|
659
|
+
});
|
|
660
|
+
if (result.stdout)
|
|
661
|
+
process.stdout.write(result.stdout);
|
|
662
|
+
if (result.stderr)
|
|
663
|
+
process.stderr.write(result.stderr);
|
|
664
|
+
process.exitCode = result.exitCode;
|
|
665
|
+
}
|
|
666
|
+
//# sourceMappingURL=cli.js.map
|