@ollie-shop/cli 0.3.4 → 1.0.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/.turbo/turbo-build.log +6 -9
- package/CHANGELOG.md +21 -0
- package/dist/index.js +986 -3956
- package/package.json +15 -37
- package/src/README.md +126 -0
- package/src/cli.tsx +45 -0
- package/src/commands/help.tsx +79 -0
- package/src/commands/login.tsx +92 -0
- package/src/commands/start.tsx +411 -0
- package/src/index.tsx +8 -0
- package/src/utils/auth.ts +218 -21
- package/src/utils/bundle.ts +177 -0
- package/src/utils/config.ts +123 -0
- package/src/utils/esbuild.ts +533 -0
- package/tsconfig.json +10 -15
- package/tsup.config.ts +7 -7
- package/CLAUDE_CLI.md +0 -265
- package/README.md +0 -711
- package/__tests__/mocks/console.ts +0 -22
- package/__tests__/mocks/core.ts +0 -137
- package/__tests__/mocks/index.ts +0 -4
- package/__tests__/mocks/inquirer.ts +0 -16
- package/__tests__/mocks/progress.ts +0 -19
- package/dist/index.d.ts +0 -1
- package/src/__tests__/helpers/cli-test-helper.ts +0 -281
- package/src/__tests__/mocks/index.ts +0 -142
- package/src/actions/component.actions.ts +0 -278
- package/src/actions/function.actions.ts +0 -220
- package/src/actions/project.actions.ts +0 -131
- package/src/actions/version.actions.ts +0 -233
- package/src/commands/__tests__/component-validation.test.ts +0 -250
- package/src/commands/__tests__/component.test.ts +0 -318
- package/src/commands/__tests__/function-validation.test.ts +0 -220
- package/src/commands/__tests__/function.test.ts +0 -286
- package/src/commands/__tests__/store-version-validation.test.ts +0 -414
- package/src/commands/__tests__/store-version.test.ts +0 -402
- package/src/commands/component.ts +0 -178
- package/src/commands/docs.ts +0 -24
- package/src/commands/function.ts +0 -201
- package/src/commands/help.ts +0 -18
- package/src/commands/index.ts +0 -27
- package/src/commands/login.ts +0 -267
- package/src/commands/project.ts +0 -107
- package/src/commands/store-version.ts +0 -242
- package/src/commands/version.ts +0 -51
- package/src/commands/whoami.ts +0 -46
- package/src/index.ts +0 -116
- package/src/prompts/component.prompts.ts +0 -94
- package/src/prompts/function.prompts.ts +0 -168
- package/src/schemas/command.schema.ts +0 -644
- package/src/types/index.ts +0 -183
- package/src/utils/__tests__/command-parser.test.ts +0 -159
- package/src/utils/__tests__/command-suggestions.test.ts +0 -185
- package/src/utils/__tests__/console.test.ts +0 -192
- package/src/utils/__tests__/context-detector.test.ts +0 -258
- package/src/utils/__tests__/enhanced-error-handler.test.ts +0 -137
- package/src/utils/__tests__/error-handler.test.ts +0 -107
- package/src/utils/__tests__/rich-progress.test.ts +0 -181
- package/src/utils/__tests__/validation-error-formatter.test.ts +0 -175
- package/src/utils/__tests__/validation-helpers.test.ts +0 -125
- package/src/utils/cli-progress-reporter.ts +0 -84
- package/src/utils/command-builder.ts +0 -390
- package/src/utils/command-helpers.ts +0 -83
- package/src/utils/command-parser.ts +0 -245
- package/src/utils/command-suggestions.ts +0 -176
- package/src/utils/console.ts +0 -320
- package/src/utils/constants.ts +0 -39
- package/src/utils/context-detector.ts +0 -177
- package/src/utils/deploy-helpers.ts +0 -357
- package/src/utils/enhanced-error-handler.ts +0 -264
- package/src/utils/error-handler.ts +0 -60
- package/src/utils/errors.ts +0 -256
- package/src/utils/interactive-builder.ts +0 -325
- package/src/utils/rich-progress.ts +0 -331
- package/src/utils/store.ts +0 -23
- package/src/utils/validation-error-formatter.ts +0 -337
- package/src/utils/validation-helpers.ts +0 -325
- package/vitest.config.ts +0 -35
- package/vitest.setup.ts +0 -29
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import * as esbuild from "esbuild";
|
|
5
|
+
import { glob } from "glob";
|
|
6
|
+
import { createComponentBundle } from "./bundle.js";
|
|
7
|
+
|
|
8
|
+
export interface ComponentInfo {
|
|
9
|
+
/** Component ID from meta.json (UUID) or generated temporary ID (studio-*) */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Folder name */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Target slot for new components (from meta.json) */
|
|
14
|
+
slot?: string;
|
|
15
|
+
entryPoint: string;
|
|
16
|
+
outfile: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ComponentMeta {
|
|
20
|
+
/** Component UUID (optional for local-only components) */
|
|
21
|
+
id?: string;
|
|
22
|
+
/** Human-readable name */
|
|
23
|
+
name?: string | null;
|
|
24
|
+
/** Default props */
|
|
25
|
+
props?: Record<string, unknown> | null;
|
|
26
|
+
/** Source path */
|
|
27
|
+
src?: string;
|
|
28
|
+
/** Deploy path */
|
|
29
|
+
deployPath?: string | null;
|
|
30
|
+
/** Target slot for local development */
|
|
31
|
+
slot?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reads the meta.json file for a component.
|
|
36
|
+
* Supports stage-specific meta files: meta.{stage}.json
|
|
37
|
+
*/
|
|
38
|
+
async function readComponentMeta(
|
|
39
|
+
componentDir: string,
|
|
40
|
+
stage?: string,
|
|
41
|
+
): Promise<ComponentMeta | null> {
|
|
42
|
+
// Try stage-specific meta first
|
|
43
|
+
if (stage && stage !== "prod") {
|
|
44
|
+
const stagePath = path.join(componentDir, `meta.${stage}.json`);
|
|
45
|
+
try {
|
|
46
|
+
const content = await fs.readFile(stagePath, "utf-8");
|
|
47
|
+
return JSON.parse(content) as ComponentMeta;
|
|
48
|
+
} catch {
|
|
49
|
+
// Fall through to default meta.json
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Read default meta.json
|
|
54
|
+
const metaPath = path.join(componentDir, "meta.json");
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs.readFile(metaPath, "utf-8");
|
|
57
|
+
return JSON.parse(content) as ComponentMeta;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface DiscoverComponentsOptions {
|
|
64
|
+
cwd?: string;
|
|
65
|
+
stage?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Discovers components in the components directory.
|
|
70
|
+
* Each component should have an index.tsx file and a meta.json with its ID.
|
|
71
|
+
*/
|
|
72
|
+
export async function discoverComponents(
|
|
73
|
+
options: DiscoverComponentsOptions = {},
|
|
74
|
+
): Promise<ComponentInfo[]> {
|
|
75
|
+
const { cwd = process.cwd(), stage } = options;
|
|
76
|
+
const componentsDir = path.join(cwd, "components");
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await fs.access(componentsDir);
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const entries = await glob("*/index.tsx", { cwd: componentsDir });
|
|
85
|
+
const components: ComponentInfo[] = [];
|
|
86
|
+
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const name = path.dirname(entry);
|
|
89
|
+
const componentDir = path.join(componentsDir, name);
|
|
90
|
+
const meta = await readComponentMeta(componentDir, stage);
|
|
91
|
+
|
|
92
|
+
// Components without id (or without meta.json) get a temporary studio-* ID
|
|
93
|
+
const id = meta?.id ?? `studio-${name}`;
|
|
94
|
+
const isUnlinked = !meta?.id;
|
|
95
|
+
|
|
96
|
+
// Unlinked components without a slot can still be built, just not placed in checkout
|
|
97
|
+
if (isUnlinked && !meta?.slot) {
|
|
98
|
+
console.warn(
|
|
99
|
+
`${name}: no slot defined - component will be built but not placed in checkout`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
components.push({
|
|
104
|
+
id,
|
|
105
|
+
name,
|
|
106
|
+
slot: meta?.slot,
|
|
107
|
+
entryPoint: path.join(componentsDir, entry),
|
|
108
|
+
outfile: path.join(cwd, "node_modules/.ollie", "build", name, "index.js"),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return components;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface BuildResult {
|
|
116
|
+
status: "ok" | "error";
|
|
117
|
+
errors: BuildError[];
|
|
118
|
+
buildTime: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CreateBuildContextOptions {
|
|
122
|
+
cwd?: string;
|
|
123
|
+
stage?: string;
|
|
124
|
+
onBuildEnd?: (components: ComponentInfo[], result: BuildResult) => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates an esbuild context for building components.
|
|
129
|
+
* Configuration matches the production build from infra/builder.
|
|
130
|
+
*/
|
|
131
|
+
export async function createBuildContext(
|
|
132
|
+
components: ComponentInfo[],
|
|
133
|
+
options: CreateBuildContextOptions = {},
|
|
134
|
+
): Promise<esbuild.BuildContext> {
|
|
135
|
+
const { cwd = process.cwd(), stage, onBuildEnd } = options;
|
|
136
|
+
const outdir = path.join(cwd, "node_modules/.ollie", "build");
|
|
137
|
+
|
|
138
|
+
// Ensure output directory exists
|
|
139
|
+
await fs.mkdir(outdir, { recursive: true });
|
|
140
|
+
|
|
141
|
+
// Create entryPoints object for named outputs
|
|
142
|
+
const entryPoints: Record<string, string> = {};
|
|
143
|
+
for (const component of components) {
|
|
144
|
+
entryPoints[`${component.name}/index`] = component.entryPoint;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Plugin to update manifest after each build
|
|
148
|
+
const manifestPlugin: esbuild.Plugin = {
|
|
149
|
+
name: "manifest-plugin",
|
|
150
|
+
setup(build) {
|
|
151
|
+
let buildStartTime = 0;
|
|
152
|
+
|
|
153
|
+
build.onStart(() => {
|
|
154
|
+
buildStartTime = Date.now();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
build.onEnd(async (result) => {
|
|
158
|
+
const buildTime = Date.now() - buildStartTime;
|
|
159
|
+
|
|
160
|
+
// Parse errors into our format
|
|
161
|
+
const errors: BuildError[] = result.errors.map((err) => {
|
|
162
|
+
// Extract component name from file path (e.g., components/Header/index.tsx -> Header)
|
|
163
|
+
const match = err.location?.file?.match(/components\/([^/]+)\//);
|
|
164
|
+
return {
|
|
165
|
+
component: match?.[1] ?? "unknown",
|
|
166
|
+
message: err.text,
|
|
167
|
+
line: err.location?.line,
|
|
168
|
+
column: err.location?.column,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const buildResult: BuildResult = {
|
|
173
|
+
status: result.errors.length > 0 ? "error" : "ok",
|
|
174
|
+
errors,
|
|
175
|
+
buildTime,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Re-discover components to get fresh metadata
|
|
179
|
+
const freshComponents = await discoverComponents({ cwd, stage });
|
|
180
|
+
|
|
181
|
+
// Always write manifest (with status info)
|
|
182
|
+
await writeManifest(freshComponents, buildResult, cwd);
|
|
183
|
+
|
|
184
|
+
// Notify callback
|
|
185
|
+
onBuildEnd?.(freshComponents, buildResult);
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const ctx = await esbuild.context({
|
|
191
|
+
entryPoints,
|
|
192
|
+
bundle: true,
|
|
193
|
+
outdir,
|
|
194
|
+
format: "cjs", // CommonJS format for module.exports.default
|
|
195
|
+
platform: "browser",
|
|
196
|
+
target: "es2020",
|
|
197
|
+
sourcemap: true,
|
|
198
|
+
metafile: true,
|
|
199
|
+
// External dependencies - these are provided by the runtime
|
|
200
|
+
external: ["react", "react-dom", "next", "@ollie-shop/sdk", "next-intl"],
|
|
201
|
+
loader: {
|
|
202
|
+
".tsx": "tsx",
|
|
203
|
+
".ts": "ts",
|
|
204
|
+
".js": "js",
|
|
205
|
+
".jsx": "jsx",
|
|
206
|
+
".css": "css",
|
|
207
|
+
},
|
|
208
|
+
logLevel: "silent", // We handle logging ourselves
|
|
209
|
+
jsx: "automatic",
|
|
210
|
+
plugins: [manifestPlugin],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return ctx;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface ServeResult {
|
|
217
|
+
host: string;
|
|
218
|
+
port: number;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Starts the esbuild serve + watch mode with a proxy server.
|
|
223
|
+
* The proxy handles /bundle/:componentName requests and forwards others to esbuild.
|
|
224
|
+
* Returns the server info and a way to stop it.
|
|
225
|
+
*/
|
|
226
|
+
export async function startDevServer(
|
|
227
|
+
ctx: esbuild.BuildContext,
|
|
228
|
+
options: {
|
|
229
|
+
port?: number;
|
|
230
|
+
host?: string;
|
|
231
|
+
cwd?: string;
|
|
232
|
+
onRequest?: (args: esbuild.ServeOnRequestArgs) => void;
|
|
233
|
+
onRebuild?: (result: esbuild.BuildResult) => void;
|
|
234
|
+
} = {},
|
|
235
|
+
): Promise<ServeResult & { stop: () => Promise<void> }> {
|
|
236
|
+
const { port = 4000, host = "localhost", cwd = process.cwd() } = options;
|
|
237
|
+
|
|
238
|
+
const servedir = path.join(cwd, "node_modules/.ollie", "build");
|
|
239
|
+
|
|
240
|
+
// Start watching for changes
|
|
241
|
+
await ctx.watch();
|
|
242
|
+
|
|
243
|
+
// Start esbuild server on internal port (proxy will forward to it)
|
|
244
|
+
const internalPort = port + 1;
|
|
245
|
+
await ctx.serve({
|
|
246
|
+
port: internalPort,
|
|
247
|
+
host,
|
|
248
|
+
servedir,
|
|
249
|
+
onRequest: options.onRequest,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Create proxy server that handles /bundle/* and forwards to esbuild
|
|
253
|
+
const proxyServer = http.createServer(async (req, res) => {
|
|
254
|
+
const url = new URL(req.url || "/", `http://${host}:${port}`);
|
|
255
|
+
|
|
256
|
+
// Handle /bundle?path=/ComponentName/index.js
|
|
257
|
+
if (url.pathname === "/bundle" && req.method === "GET") {
|
|
258
|
+
const componentPath = url.searchParams.get("path");
|
|
259
|
+
|
|
260
|
+
if (!componentPath) {
|
|
261
|
+
res.statusCode = 400;
|
|
262
|
+
res.setHeader("Content-Type", "application/json");
|
|
263
|
+
res.end(JSON.stringify({ error: "Missing 'path' query parameter" }));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Extract component name from path (e.g., /Header/index.js → Header)
|
|
268
|
+
const pathMatch = componentPath.match(/^\/?([^/]+)\//);
|
|
269
|
+
if (!pathMatch) {
|
|
270
|
+
res.statusCode = 400;
|
|
271
|
+
res.setHeader("Content-Type", "application/json");
|
|
272
|
+
res.end(
|
|
273
|
+
JSON.stringify({ error: `Invalid path format: ${componentPath}` }),
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const componentName = pathMatch[1];
|
|
279
|
+
|
|
280
|
+
// Set CORS headers
|
|
281
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
282
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const stream = await createComponentBundle({ cwd, componentName });
|
|
286
|
+
|
|
287
|
+
res.setHeader("Content-Type", "application/zip");
|
|
288
|
+
res.setHeader(
|
|
289
|
+
"Content-Disposition",
|
|
290
|
+
`attachment; filename="${componentName}.zip"`,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
stream.pipe(res);
|
|
294
|
+
|
|
295
|
+
stream.on("error", (err) => {
|
|
296
|
+
console.error("[Bundle] Stream error:", err);
|
|
297
|
+
if (!res.headersSent) {
|
|
298
|
+
res.statusCode = 500;
|
|
299
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
} catch (err) {
|
|
303
|
+
res.statusCode = 400;
|
|
304
|
+
res.setHeader("Content-Type", "application/json");
|
|
305
|
+
res.end(
|
|
306
|
+
JSON.stringify({
|
|
307
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Handle POST /meta?component=ComponentName to update meta.json
|
|
315
|
+
if (url.pathname === "/meta" && req.method === "POST") {
|
|
316
|
+
const componentName = url.searchParams.get("component");
|
|
317
|
+
|
|
318
|
+
if (!componentName) {
|
|
319
|
+
res.statusCode = 400;
|
|
320
|
+
res.setHeader("Content-Type", "application/json");
|
|
321
|
+
res.end(
|
|
322
|
+
JSON.stringify({ error: "Missing 'component' query parameter" }),
|
|
323
|
+
);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Set CORS headers
|
|
328
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
329
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
330
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
331
|
+
|
|
332
|
+
// Read request body
|
|
333
|
+
let body = "";
|
|
334
|
+
req.on("data", (chunk) => {
|
|
335
|
+
body += chunk.toString();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
req.on("end", async () => {
|
|
339
|
+
try {
|
|
340
|
+
const updates = JSON.parse(body) as Record<string, unknown>;
|
|
341
|
+
const metaPath = path.join(
|
|
342
|
+
cwd,
|
|
343
|
+
"components",
|
|
344
|
+
componentName,
|
|
345
|
+
"meta.json",
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// Read existing meta.json or create new one
|
|
349
|
+
let existingMeta: Record<string, unknown> = {};
|
|
350
|
+
try {
|
|
351
|
+
const content = await fs.readFile(metaPath, "utf-8");
|
|
352
|
+
existingMeta = JSON.parse(content) as Record<string, unknown>;
|
|
353
|
+
} catch {
|
|
354
|
+
// File doesn't exist, start fresh
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Merge updates
|
|
358
|
+
const newMeta = { ...existingMeta, ...updates };
|
|
359
|
+
|
|
360
|
+
// Write back
|
|
361
|
+
await fs.writeFile(metaPath, JSON.stringify(newMeta, null, 2));
|
|
362
|
+
|
|
363
|
+
res.setHeader("Content-Type", "application/json");
|
|
364
|
+
res.end(JSON.stringify({ success: true, meta: newMeta }));
|
|
365
|
+
} catch (err) {
|
|
366
|
+
res.statusCode = 400;
|
|
367
|
+
res.setHeader("Content-Type", "application/json");
|
|
368
|
+
res.end(
|
|
369
|
+
JSON.stringify({
|
|
370
|
+
error: err instanceof Error ? err.message : "Invalid JSON body",
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Handle CORS preflight
|
|
380
|
+
if (req.method === "OPTIONS") {
|
|
381
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
382
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
383
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
384
|
+
res.statusCode = 204;
|
|
385
|
+
res.end();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Proxy all other requests to esbuild
|
|
390
|
+
const proxyReq = http.request(
|
|
391
|
+
{
|
|
392
|
+
hostname: host,
|
|
393
|
+
port: internalPort,
|
|
394
|
+
path: req.url,
|
|
395
|
+
method: req.method,
|
|
396
|
+
headers: req.headers,
|
|
397
|
+
},
|
|
398
|
+
(proxyRes) => {
|
|
399
|
+
// Add CORS headers to proxied responses
|
|
400
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
401
|
+
|
|
402
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
403
|
+
proxyRes.pipe(res);
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
proxyReq.on("error", (err) => {
|
|
408
|
+
console.error("[Proxy] Error:", err);
|
|
409
|
+
res.statusCode = 502;
|
|
410
|
+
res.end("Bad Gateway");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
req.pipe(proxyReq);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Start proxy server
|
|
417
|
+
await new Promise<void>((resolve, reject) => {
|
|
418
|
+
proxyServer.on("error", reject);
|
|
419
|
+
proxyServer.listen(port, host, () => resolve());
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
host,
|
|
424
|
+
port,
|
|
425
|
+
stop: async () => {
|
|
426
|
+
proxyServer.close();
|
|
427
|
+
await ctx.dispose();
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Gets the list of output files from the last build.
|
|
434
|
+
*/
|
|
435
|
+
export function getOutputFiles(
|
|
436
|
+
metafile: esbuild.Metafile | undefined,
|
|
437
|
+
cwd: string = process.cwd(),
|
|
438
|
+
): string[] {
|
|
439
|
+
if (!metafile) return [];
|
|
440
|
+
|
|
441
|
+
return Object.keys(metafile.outputs).map((output) =>
|
|
442
|
+
path.relative(cwd, output),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Component manifest entry for the admin app.
|
|
448
|
+
* Components with id starting with "studio-" are unlinked (not in database).
|
|
449
|
+
*/
|
|
450
|
+
export interface ManifestEntry {
|
|
451
|
+
/** Component UUID from meta.json or generated studio ID (studio-*) */
|
|
452
|
+
id: string;
|
|
453
|
+
/** Folder name */
|
|
454
|
+
name: string;
|
|
455
|
+
/** URL path to JS file */
|
|
456
|
+
js: string;
|
|
457
|
+
/** URL path to CSS file (if exists) */
|
|
458
|
+
css?: string;
|
|
459
|
+
/** Target slot for components */
|
|
460
|
+
slot?: string;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Build error info for the admin app.
|
|
465
|
+
*/
|
|
466
|
+
export interface BuildError {
|
|
467
|
+
/** Component name (folder) where the error occurred */
|
|
468
|
+
component: string;
|
|
469
|
+
/** Error message */
|
|
470
|
+
message: string;
|
|
471
|
+
/** Line number if available */
|
|
472
|
+
line?: number;
|
|
473
|
+
/** Column number if available */
|
|
474
|
+
column?: number;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Build manifest with status info.
|
|
479
|
+
*/
|
|
480
|
+
export interface BuildManifest {
|
|
481
|
+
/** Build status */
|
|
482
|
+
status: "ok" | "error";
|
|
483
|
+
/** Build errors if any */
|
|
484
|
+
errors: BuildError[];
|
|
485
|
+
/** Build duration in milliseconds */
|
|
486
|
+
buildTime: number;
|
|
487
|
+
/** Component entries */
|
|
488
|
+
components: ManifestEntry[];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Writes a manifest.json file to the build directory.
|
|
493
|
+
* This allows the admin app to map component names to IDs.
|
|
494
|
+
*/
|
|
495
|
+
export async function writeManifest(
|
|
496
|
+
components: ComponentInfo[],
|
|
497
|
+
buildResult: BuildResult,
|
|
498
|
+
cwd: string = process.cwd(),
|
|
499
|
+
): Promise<void> {
|
|
500
|
+
const outdir = path.join(cwd, "node_modules/.ollie", "build");
|
|
501
|
+
const manifestPath = path.join(outdir, "manifest.json");
|
|
502
|
+
|
|
503
|
+
const componentEntries: ManifestEntry[] = await Promise.all(
|
|
504
|
+
components.map(async (c) => {
|
|
505
|
+
const entry: ManifestEntry = {
|
|
506
|
+
id: c.id,
|
|
507
|
+
name: c.name,
|
|
508
|
+
js: `/${c.name}/index.js`,
|
|
509
|
+
slot: c.slot,
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// Check if CSS exists
|
|
513
|
+
const cssPath = path.join(outdir, c.name, "index.css");
|
|
514
|
+
try {
|
|
515
|
+
await fs.access(cssPath);
|
|
516
|
+
entry.css = `/${c.name}/index.css`;
|
|
517
|
+
} catch {
|
|
518
|
+
// No CSS file
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return entry;
|
|
522
|
+
}),
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const manifest: BuildManifest = {
|
|
526
|
+
status: buildResult.status,
|
|
527
|
+
errors: buildResult.errors,
|
|
528
|
+
buildTime: buildResult.buildTime,
|
|
529
|
+
components: componentEntries,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
533
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"
|
|
4
|
-
"module": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
7
|
"jsx": "react-jsx",
|
|
8
8
|
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
12
|
"declaration": true,
|
|
10
13
|
"declarationMap": true,
|
|
11
|
-
"noUncheckedIndexedAccess": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"allowSyntheticDefaultImports": true,
|
|
15
|
-
"resolveJsonModule": true,
|
|
16
14
|
"outDir": "dist",
|
|
17
15
|
"rootDir": "src",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"@/*": ["./src/*"],
|
|
21
|
-
"@tests/*": ["./src/__tests__/*"]
|
|
22
|
-
}
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true
|
|
23
18
|
},
|
|
24
19
|
"include": ["src/**/*"],
|
|
25
|
-
"exclude": ["node_modules", "dist"
|
|
20
|
+
"exclude": ["node_modules", "dist"]
|
|
26
21
|
}
|
package/tsup.config.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineConfig } from "tsup";
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
entry: ["src/index.
|
|
5
|
-
format: ["
|
|
6
|
-
target: "
|
|
7
|
-
|
|
4
|
+
entry: ["src/index.tsx"],
|
|
5
|
+
format: ["esm"],
|
|
6
|
+
target: "node22",
|
|
7
|
+
dts: false,
|
|
8
8
|
clean: true,
|
|
9
|
-
dts: true,
|
|
10
9
|
sourcemap: false,
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
banner: {
|
|
11
|
+
js: "#!/usr/bin/env node",
|
|
12
|
+
},
|
|
13
13
|
});
|