kirbyup 4.0.0-alpha.3 → 4.0.0-alpha.5

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/dist/node/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as version, i as name, n as serve, r as handleError, t as build } from "../node-DxNmVk8V.mjs";
1
+ import { a as version, i as name, n as serve, r as handleError, t as build } from "../node-B8aLFfzt.mjs";
2
2
  import { cac } from "cac";
3
3
 
4
4
  //#region src/node/cli-start.ts
@@ -1,3 +1,3 @@
1
- import { n as serve, t as build } from "../node-DxNmVk8V.mjs";
1
+ import { n as serve, t as build } from "../node-B8aLFfzt.mjs";
2
2
 
3
3
  export { build, serve };
@@ -0,0 +1,496 @@
1
+ import * as fs from "node:fs";
2
+ import * as fsp from "node:fs/promises";
3
+ import vuePlugin from "@vitejs/plugin-vue";
4
+ import vueJsxPlugin from "@vitejs/plugin-vue-jsx";
5
+ import { consola } from "consola";
6
+ import { colors } from "consola/utils";
7
+ import { basename, dirname, normalize, relative, resolve } from "pathe";
8
+ import { debounce } from "perfect-debounce";
9
+ import { build, createLogger, mergeConfig } from "vite";
10
+ import fullReloadPlugin from "vite-plugin-full-reload";
11
+ import * as vueCompilerSfc from "vue/compiler-sfc";
12
+ import { loadConfig } from "c12";
13
+ import postcssrc from "postcss-load-config";
14
+ import { detectPackageManager } from "nypm";
15
+ import { isIP } from "node:net";
16
+ import { Buffer } from "node:buffer";
17
+ import { promisify } from "node:util";
18
+ import { gzip } from "node:zlib";
19
+
20
+ //#region package.json
21
+ var name = "kirbyup";
22
+ var version = "4.0.0-alpha.5";
23
+
24
+ //#endregion
25
+ //#region src/node/config.ts
26
+ function loadConfig$1(cwd = process.cwd()) {
27
+ return loadConfig({
28
+ cwd,
29
+ name: "kirbyup",
30
+ rcFile: false,
31
+ packageJson: false
32
+ });
33
+ }
34
+ async function resolvePostCSSConfig(cwd) {
35
+ try {
36
+ return await postcssrc(void 0, void 0, { stopDir: cwd });
37
+ } catch (error) {
38
+ if (!error.message.includes("No PostCSS Config found")) throw error;
39
+ }
40
+ }
41
+
42
+ //#endregion
43
+ //#region src/node/errors.ts
44
+ var PrettyError = class extends Error {
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = this.constructor.name;
48
+ if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, this.constructor);
49
+ else this.stack = new Error(message).stack;
50
+ }
51
+ };
52
+ function handleError(error) {
53
+ consola.error(error.message);
54
+ process.exitCode = 1;
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/node/plugins/build-cleanup.ts
59
+ function kirbyupBuildCleanupPlugin(options) {
60
+ let config;
61
+ let devIndexPath;
62
+ return {
63
+ name: "kirbyup:build-cleanup",
64
+ configResolved(resolvedConfig) {
65
+ config = resolvedConfig;
66
+ devIndexPath = resolve(config.root, options.outDir, "index.dev.mjs");
67
+ },
68
+ writeBundle() {
69
+ if (fs.existsSync(devIndexPath)) fs.unlinkSync(devIndexPath);
70
+ }
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ //#region src/node/utils/server.ts
76
+ function resolveOriginFromServerOptions(serverOptions, port, fallbackHostname) {
77
+ const protocol = serverOptions?.https && (typeof serverOptions.https === "boolean" || Object.keys(serverOptions.https).length > 0) ? "https" : "http";
78
+ const configuredHost = normalizeHost(serverOptions?.host);
79
+ return `${protocol}://${formatHostname(configuredHost || fallbackHostname || "localhost")}${(configuredHost ? hostIncludesPort(configuredHost) : false) || !needsExplicitPort(protocol, port) ? "" : `:${port}`}`;
80
+ }
81
+ function ensureTrailingSlash(url) {
82
+ return url.endsWith("/") ? url : `${url}/`;
83
+ }
84
+ function normalizeHost(host) {
85
+ if (host === false || host === void 0) return;
86
+ if (host === true) return "0.0.0.0";
87
+ return host;
88
+ }
89
+ function formatHostname(host) {
90
+ if (!host) return "localhost";
91
+ if (host.startsWith("[") && host.endsWith("]")) return host;
92
+ return isIP(host) === 6 ? `[${host}]` : host;
93
+ }
94
+ function hostIncludesPort(host) {
95
+ if (host.startsWith("[")) {
96
+ const closingIndex = host.indexOf("]");
97
+ return closingIndex > -1 && host.slice(closingIndex + 1).startsWith(":");
98
+ }
99
+ if (isIP(host) === 6) return false;
100
+ return host.includes(":");
101
+ }
102
+ function needsExplicitPort(protocol, port) {
103
+ if (protocol === "http") return port !== 80;
104
+ return port !== 443;
105
+ }
106
+
107
+ //#endregion
108
+ //#region src/node/plugins/utils.ts
109
+ /**
110
+ * This code is injected into Vue 3 components to wrap the global HMR runtime.
111
+ *
112
+ * All `.vue` components register themselves with the HMR runtime (`__VUE_HMR_RUNTIME__`),
113
+ * which stores their component definitions in a map. When a module is updated, the runtime
114
+ * applies changes to the stored definition and re-renders component instances.
115
+ *
116
+ * In Vue 3, the HMR map structure is:
117
+ * ```js
118
+ * {
119
+ * [id]: { initialDef: ComponentDefinition, instances: Set<ComponentInstance> }
120
+ * }
121
+ * ```
122
+ *
123
+ * However, Kirby does not register the exact object exported from a `.vue` file.
124
+ * Instead, it creates a new object and merges the definition in:
125
+ * https://github.com/getkirby/kirby/blob/main/panel/public/js/plugins.js
126
+ *
127
+ * After HMR changes, the runtime updates the stored definition and re-renders instances,
128
+ * but since they are derived from Kirby's modified object (not the stored definition),
129
+ * updates may not apply correctly.
130
+ *
131
+ * To fix this, we wrap `__VUE_HMR_RUNTIME__.rerender()` and `__VUE_HMR_RUNTIME__.reload()`:
132
+ *
133
+ * 1. **Before updates**, we check if the component belongs to a Kirby plugin by matching
134
+ * the `__hmrId` or `__file` properties against registered plugin components.
135
+ *
136
+ * 2. **If matched**, we look up the actual component definition used by Kirby
137
+ * (`window.panel.app.config.globalProperties.$root.$options.components`)
138
+ * and update the HMR map's reference to point to it.
139
+ *
140
+ * 3. **For section components** (detected by `k-*-section` name pattern), we add a
141
+ * `$_isSection` flag for special treatment in `$_applyKirbyModifications()`.
142
+ *
143
+ * `$_applyKirbyModifications()`:
144
+ *
145
+ * Kirby modifies component definitions before registration:
146
+ * - Adds the section mixin to section components
147
+ * - Gives templates priority over render functions
148
+ * - Resolves component names in `extends` to their definition objects
149
+ *
150
+ * When a module hot reloads, Vue 3 receives a fresh component definition missing
151
+ * these modifications. We re-apply them to ensure the runtime doesn't prune them
152
+ * when patching the stored definition with the updated one.
153
+ *
154
+ * This code uses a singleton pattern (`__KIRBYUP_HMR_WRAPPED__`) to ensure the
155
+ * wrapper is installed only once, even though it's injected into every component.
156
+ */
157
+ const __HMR_INJECTION_CODE__ = `
158
+ /** - injected by kirbyup for Vue 3 HMR - */
159
+ if (typeof __VUE_HMR_RUNTIME__ !== 'undefined' && !window.__KIRBYUP_HMR_WRAPPED__) {
160
+ window.__KIRBYUP_HMR_WRAPPED__ = true;
161
+
162
+ const originalRerender = __VUE_HMR_RUNTIME__.rerender;
163
+ const originalReload = __VUE_HMR_RUNTIME__.reload;
164
+
165
+ __VUE_HMR_RUNTIME__.rerender = function(id, newRender) {
166
+ $_syncKirbyComponent(id);
167
+ return originalRerender.call(this, id, newRender);
168
+ };
169
+
170
+ __VUE_HMR_RUNTIME__.reload = function(id, newComp) {
171
+ const record = $_getHmrRecord(id);
172
+ if (record) {
173
+ $_syncKirbyComponent(id, record.initialDef);
174
+ $_applyKirbyModifications(record.initialDef, newComp);
175
+ }
176
+ return originalReload.call(this, id, newComp);
177
+ };
178
+
179
+ function $_getHmrRecord(id) {
180
+ // Vue 3's HMR map is not exposed, so we maintain our own parallel map
181
+ // by wrapping \`createRecord\` to track component definitions
182
+ if (!window.__KIRBYUP_MAP__) {
183
+ window.__KIRBYUP_MAP__ = new Map();
184
+ const originalCreate = __VUE_HMR_RUNTIME__.createRecord;
185
+ __VUE_HMR_RUNTIME__.createRecord = function(id, initialDef) {
186
+ window.__KIRBYUP_MAP__.set(id, { initialDef });
187
+ return originalCreate.call(this, id, initialDef);
188
+ };
189
+ }
190
+ return window.__KIRBYUP_MAP__.get(id);
191
+ }
192
+
193
+ function $_syncKirbyComponent(id, activeDef) {
194
+ const pluginComponents = window.panel?.plugins?.components;
195
+ const usedComponentDefs = window.panel?.app?.config?.globalProperties?.$root?.$options?.components;
196
+
197
+ if (!pluginComponents || !usedComponentDefs) return;
198
+
199
+ const record = $_getHmrRecord(id);
200
+ if (!record) return;
201
+
202
+ for (const componentName in pluginComponents) {
203
+ const pluginComp = pluginComponents[componentName];
204
+
205
+ if (pluginComp.__hmrId === id || pluginComp.__file === record.initialDef?.__file) {
206
+ const usedDef = usedComponentDefs[componentName];
207
+
208
+ if (usedDef && record.initialDef !== usedDef) {
209
+ record.initialDef = usedDef;
210
+ }
211
+
212
+ if (activeDef && typeof activeDef.$_isSection !== 'boolean') {
213
+ activeDef.$_isSection = /^k-.*-section$/.test(componentName);
214
+ }
215
+
216
+ break;
217
+ }
218
+ }
219
+ }
220
+
221
+ function $_applyKirbyModifications(activeDef, newDef) {
222
+ const usedComponentDefs = window.panel?.app?.config?.globalProperties?.$root?.$options?.components;
223
+
224
+ if (!usedComponentDefs) return;
225
+
226
+ // Give templates priority over render functions
227
+ if (newDef.template) {
228
+ newDef.render = null;
229
+ }
230
+
231
+ // Re-apply section mixin for section components
232
+ if (activeDef.$_isSection) {
233
+ newDef.$_isSection = true;
234
+ if (!newDef.mixins?.[0]?.methods?.load) {
235
+ const sectionMixin = activeDef.mixins?.[0];
236
+ if (sectionMixin) {
237
+ newDef.mixins = [sectionMixin, ...(newDef.mixins || [])];
238
+ }
239
+ }
240
+ }
241
+
242
+ // Resolve component name in extends to definition object
243
+ if (typeof newDef.extends === 'string') {
244
+ if (newDef.extends === activeDef.extends?.name) {
245
+ newDef.extends = activeDef.extends;
246
+ } else if (usedComponentDefs[newDef.extends]) {
247
+ newDef.extends = usedComponentDefs[newDef.extends];
248
+ } else {
249
+ newDef.extends = null;
250
+ }
251
+ }
252
+ }
253
+ }
254
+ /** -- */
255
+ `;
256
+
257
+ //#endregion
258
+ //#region src/node/plugins/hmr.ts
259
+ function kirbyupHmrPlugin(options) {
260
+ let config;
261
+ let entry;
262
+ let devIndexPath;
263
+ return {
264
+ name: "kirbyup:hmr",
265
+ apply: "serve",
266
+ configResolved(resolvedConfig) {
267
+ config = resolvedConfig;
268
+ entry = resolve(config.root, options.entry);
269
+ devIndexPath = resolve(config.root, options.outDir ?? "", "index.dev.mjs");
270
+ },
271
+ transform(code, id) {
272
+ if (!id.endsWith(".vue")) return;
273
+ if (!code.includes("__VUE_HMR_RUNTIME__.createRecord")) return;
274
+ const injectionPoint = code.indexOf("import.meta.hot.accept");
275
+ if (injectionPoint === -1) return;
276
+ return {
277
+ code: `${code.slice(0, injectionPoint) + __HMR_INJECTION_CODE__}\n${code.slice(injectionPoint)}`,
278
+ map: null
279
+ };
280
+ },
281
+ configureServer(server) {
282
+ if (!server.httpServer) return;
283
+ server.httpServer.once("listening", async () => {
284
+ const entryPath = entry.replace(`${config.root}/`, "");
285
+ const baseUrl = getDevBaseUrl(server, config);
286
+ const entryUrl = new URL(entryPath, baseUrl).href;
287
+ const pm = await detectPackageManager(config.root);
288
+ await fsp.writeFile(devIndexPath, getViteProxyModule(entryUrl, pm));
289
+ });
290
+ },
291
+ async closeBundle() {
292
+ await fsp.rm(devIndexPath, { force: true });
293
+ }
294
+ };
295
+ }
296
+ /**
297
+ * Proxy the JS file to "forward" the plugin script loaded by Kirby to the Vite server
298
+ */
299
+ function getViteProxyModule(entryUrl, packageManager) {
300
+ const pm = packageManager?.name || "npm";
301
+ return `
302
+ try {
303
+ await import("${entryUrl}");
304
+ } catch (error) {
305
+ console.error(
306
+ "[kirbyup] Couldn't connect to the development server at ${entryUrl}. Run \`${pm} run serve\` to start Vite or build the plugin with \`${pm} run build\` so Kirby uses the production version."
307
+ );
308
+ throw error;
309
+ }
310
+ `.trimStart();
311
+ }
312
+ function getDevBaseUrl(server, config) {
313
+ const { address, port } = server.httpServer.address();
314
+ const normalizedOrigin = ensureTrailingSlash(config.server?.origin ?? server.resolvedUrls?.local?.[0] ?? server.resolvedUrls?.network?.[0] ?? resolveOriginFromServerOptions(config.server, port, address));
315
+ const base = config.base ?? "/";
316
+ return new URL(base, normalizedOrigin).href;
317
+ }
318
+
319
+ //#endregion
320
+ //#region src/node/utils.ts
321
+ const compress = promisify(gzip);
322
+ function toArray(array) {
323
+ array ??= [];
324
+ return Array.isArray(array) ? array : [array];
325
+ }
326
+ async function getCompressedSize(code) {
327
+ return ` / gzip: ${((await compress(typeof code === "string" ? code : Buffer.from(code))).length / 1024).toFixed(2)} KiB`;
328
+ }
329
+ async function printFileInfo({ root, outDir, filePath, content, type, maxLength }) {
330
+ const prettyOutDir = `${normalize(relative(root, resolve(root, outDir)))}/`;
331
+ const kibs = content.length / 1024;
332
+ const compressedSize = await getCompressedSize(content);
333
+ const writeColor = type === "chunk" ? colors.cyan : colors.magenta;
334
+ consola.log(colors.white(colors.dim(prettyOutDir)) + writeColor(filePath.padEnd(maxLength + 2)) + colors.dim(`${kibs.toFixed(2)} kB${compressedSize}`));
335
+ }
336
+
337
+ //#endregion
338
+ //#region src/node/index.ts
339
+ const DEV_OUTPUT_FILENAME = "index.dev.js";
340
+ let resolvedKirbyupConfig;
341
+ let resolvedPostCssConfig;
342
+ const logLevel = "warn";
343
+ const logger = createLogger(logLevel);
344
+ const loggerWarn = logger.warn;
345
+ logger.warn = (msg, options) => {
346
+ if (msg.includes("(!) build.outDir")) return;
347
+ loggerWarn(msg, options);
348
+ };
349
+ function getViteConfig(command, options) {
350
+ const aliasDir = resolve(options.cwd, dirname(options.entry));
351
+ const { alias = {}, vite } = resolvedKirbyupConfig;
352
+ const userConfig = vite ?? {};
353
+ const sharedConfig = {
354
+ resolve: { alias: {
355
+ "~/": `${aliasDir}/`,
356
+ "@/": `${aliasDir}/`,
357
+ ...alias
358
+ } },
359
+ plugins: [vuePlugin({ compiler: vueCompilerSfc }), vueJsxPlugin()],
360
+ build: { copyPublicDir: false },
361
+ ...resolvedPostCssConfig && { css: { postcss: resolvedPostCssConfig } },
362
+ envDir: options.cwd,
363
+ envPrefix: ["VITE_", "KIRBYUP_"],
364
+ customLogger: logger,
365
+ logLevel
366
+ };
367
+ if (command === "serve") {
368
+ const { port, watch } = options;
369
+ const inferredOrigin = userConfig.server?.origin ?? resolveOriginFromServerOptions(userConfig.server, port, "localhost");
370
+ return mergeConfig(mergeConfig(sharedConfig, {
371
+ plugins: [kirbyupHmrPlugin(options), watch && fullReloadPlugin(watch)].filter(Boolean),
372
+ build: { rollupOptions: { input: resolve(options.cwd, options.entry) } },
373
+ server: {
374
+ port,
375
+ strictPort: true,
376
+ origin: inferredOrigin
377
+ }
378
+ }), userConfig);
379
+ }
380
+ const mode = options.watch ? "development" : "production";
381
+ return mergeConfig(mergeConfig(sharedConfig, {
382
+ mode,
383
+ plugins: [kirbyupBuildCleanupPlugin(options)],
384
+ build: {
385
+ lib: {
386
+ entry: resolve(options.cwd, options.entry),
387
+ formats: ["es"],
388
+ fileName: () => options.watch ? DEV_OUTPUT_FILENAME : "index.js"
389
+ },
390
+ minify: mode === "production",
391
+ outDir: options.outDir,
392
+ emptyOutDir: false,
393
+ rollupOptions: {
394
+ external: ["vue"],
395
+ output: { assetFileNames: "index.[ext]" }
396
+ }
397
+ }
398
+ }), userConfig);
399
+ }
400
+ async function generate(options) {
401
+ const config = getViteConfig("build", options);
402
+ let result;
403
+ try {
404
+ result = await build(config);
405
+ } catch (error) {
406
+ if (config.mode === "production") throw error;
407
+ else consola.error(error);
408
+ }
409
+ if (result && !options.watch) {
410
+ const { output } = toArray(result)[0];
411
+ let maxLength = 0;
412
+ for (const chunkFile in output) {
413
+ const fileNameLength = output[chunkFile].fileName.length;
414
+ if (fileNameLength > maxLength) maxLength = fileNameLength;
415
+ }
416
+ for (const { fileName, type, code } of output) {
417
+ const content = code || await fsp.readFile(resolve(options.outDir, fileName), "utf8");
418
+ await printFileInfo({
419
+ root: options.cwd,
420
+ outDir: options.outDir,
421
+ filePath: fileName,
422
+ content,
423
+ type,
424
+ maxLength
425
+ });
426
+ }
427
+ }
428
+ return result;
429
+ }
430
+ async function build$1(options) {
431
+ assertEntryExists(options);
432
+ const { cwd } = options;
433
+ const { config, configFile } = await loadConfig$1(cwd);
434
+ resolvedKirbyupConfig = config ?? {};
435
+ resolvedPostCssConfig = await resolvePostCSSConfig(cwd);
436
+ consola.log(colors.green(`${name} v${version}`));
437
+ consola.start(`Building ${colors.cyan(options.entry)}`);
438
+ if (options.watch) consola.info("Running in watch mode");
439
+ await generate(options);
440
+ consola.success("Build successful");
441
+ if (!options.watch) return;
442
+ const { watch } = await import("chokidar");
443
+ const debouncedBuild = debounce(async () => {
444
+ generate(options).catch(handleError);
445
+ }, 100);
446
+ const ignored = [
447
+ "**/{.git,node_modules}/**",
448
+ "index.{css,js}",
449
+ DEV_OUTPUT_FILENAME
450
+ ];
451
+ const watchPaths = typeof options.watch === "boolean" ? dirname(options.entry) : Array.isArray(options.watch) ? options.watch.filter((path) => typeof path === "string") : options.watch;
452
+ consola.info(`Watching for changes in ${toArray(watchPaths).map((i) => colors.cyan(i)).join(", ")}`);
453
+ const watcher = watch(watchPaths, {
454
+ ignoreInitial: true,
455
+ ignorePermissionErrors: true,
456
+ ignored,
457
+ cwd
458
+ });
459
+ const devOutputPath = resolve(options.outDir, DEV_OUTPUT_FILENAME);
460
+ const cleanup = async () => {
461
+ await watcher.close().catch(() => {});
462
+ await fsp.rm(devOutputPath, { force: true }).catch(() => {});
463
+ };
464
+ process.once("exit", () => {
465
+ try {
466
+ fs.rmSync(devOutputPath, { force: true });
467
+ } catch {}
468
+ });
469
+ const onShutdown = () => void cleanup().finally(() => process.exit(0));
470
+ process.once("SIGINT", onShutdown);
471
+ process.once("SIGTERM", onShutdown);
472
+ if (configFile) watcher.add(configFile);
473
+ watcher.on("all", async (type, file) => {
474
+ if (configFile === resolve(cwd, file)) {
475
+ resolvedKirbyupConfig = (await loadConfig$1(cwd)).config ?? {};
476
+ consola.info(`${colors.cyan(basename(file))} changed, setting new config`);
477
+ } else consola.log(`${colors.green(type)} ${colors.white(colors.dim(file))}`);
478
+ debouncedBuild();
479
+ });
480
+ }
481
+ async function serve(options) {
482
+ assertEntryExists(options);
483
+ const { cwd } = options;
484
+ const { config } = await loadConfig$1(cwd);
485
+ resolvedKirbyupConfig = config ?? {};
486
+ resolvedPostCssConfig = await resolvePostCSSConfig(cwd);
487
+ consola.log(colors.green(`${name} v${version}`));
488
+ consola.info(`Development server unavailable. Use watch mode for now: ${colors.cyan(`kirbyup build ${options.entry} --watch`)}`);
489
+ throw new PrettyError("HMR is not yet implemented for Kirby 6 plugins. Please use watch mode instead.");
490
+ }
491
+ function assertEntryExists(options) {
492
+ if (!fs.existsSync(resolve(options.cwd, options.entry))) throw new PrettyError(`Cannot find "${options.entry}"`);
493
+ }
494
+
495
+ //#endregion
496
+ export { version as a, name as i, serve as n, handleError as r, build$1 as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kirbyup",
3
3
  "type": "module",
4
- "version": "4.0.0-alpha.3",
4
+ "version": "4.0.0-alpha.5",
5
5
  "packageManager": "pnpm@10.25.0",
6
6
  "description": "Zero-config bundler for Kirby Panel plugins",
7
7
  "author": {
@@ -84,7 +84,6 @@
84
84
  "perfect-debounce": "^2.0.0",
85
85
  "postcss": "^8.5.6",
86
86
  "postcss-load-config": "^6.0.1",
87
- "rollup-plugin-external-globals": "^0.13.0",
88
87
  "sass": "^1.95.1",
89
88
  "vite": "^7.2.7",
90
89
  "vite-plugin-full-reload": "^1.2.0",
@@ -1,491 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as fsp from "node:fs/promises";
3
- import vuePlugin from "@vitejs/plugin-vue";
4
- import vueJsxPlugin from "@vitejs/plugin-vue-jsx";
5
- import { consola } from "consola";
6
- import { colors } from "consola/utils";
7
- import { basename, dirname, normalize, relative, resolve } from "pathe";
8
- import { debounce } from "perfect-debounce";
9
- import { build, createLogger, mergeConfig } from "vite";
10
- import fullReloadPlugin from "vite-plugin-full-reload";
11
- import * as vueCompilerSfc from "vue/compiler-sfc";
12
- import { loadConfig } from "c12";
13
- import postcssrc from "postcss-load-config";
14
- import { detectPackageManager } from "nypm";
15
- import { Buffer } from "node:buffer";
16
- import { promisify } from "node:util";
17
- import { gzip } from "node:zlib";
18
-
19
- //#region package.json
20
- var name = "kirbyup";
21
- var version = "4.0.0-alpha.3";
22
-
23
- //#endregion
24
- //#region src/node/config.ts
25
- function loadConfig$1(cwd = process.cwd()) {
26
- return loadConfig({
27
- cwd,
28
- name: "kirbyup",
29
- rcFile: false,
30
- packageJson: false
31
- });
32
- }
33
- async function resolvePostCSSConfig(cwd) {
34
- try {
35
- return await postcssrc(void 0, void 0, { stopDir: cwd });
36
- } catch (error) {
37
- if (!error.message.includes("No PostCSS Config found")) throw error;
38
- }
39
- }
40
-
41
- //#endregion
42
- //#region src/node/errors.ts
43
- var PrettyError = class extends Error {
44
- constructor(message) {
45
- super(message);
46
- this.name = this.constructor.name;
47
- if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, this.constructor);
48
- else this.stack = new Error(message).stack;
49
- }
50
- };
51
- function handleError(error) {
52
- consola.error(error.message);
53
- process.exitCode = 1;
54
- }
55
-
56
- //#endregion
57
- //#region src/node/plugins/build-cleanup.ts
58
- function kirbyupBuildCleanupPlugin(options) {
59
- let config;
60
- let devIndexPath;
61
- return {
62
- name: "kirbyup:build-cleanup",
63
- configResolved(resolvedConfig) {
64
- config = resolvedConfig;
65
- devIndexPath = resolve(config.root, options.outDir, "index.dev.mjs");
66
- },
67
- writeBundle() {
68
- if (fs.existsSync(devIndexPath)) fs.unlinkSync(devIndexPath);
69
- }
70
- };
71
- }
72
-
73
- //#endregion
74
- //#region src/node/plugins/utils.ts
75
- const HMR_RUNTIME_ID = "\0plugin-vue2:hmr-runtime";
76
- const JSX_HMR_RUNTIME_ID = "plugin-vue2-jsx:hmr-runtime";
77
- function isHmrRuntimeId(id) {
78
- return id === HMR_RUNTIME_ID || id === JSX_HMR_RUNTIME_ID;
79
- }
80
- /**
81
- * This code is injected into the HMR runtime of plugin-vue2(-jsx).
82
- *
83
- * All `.vue` components register themselves once with the HMR runtime, so their exported
84
- * component definitions can be stored in a map, alongside the rendered component instances
85
- * that are based off this definition. When a module is updated, the runtime applies all
86
- * changes from the updated module to the stored definition, then re-renders the instances.
87
- *
88
- * ```js
89
- * {
90
- * [id]: { options: ComponentDefinition, instances: [...] }
91
- * }
92
- * ```
93
- *
94
- * However, in some cases (sections and blocks) Kirby does not actually register the
95
- * object that is exported from a `.vue` file (and stored as definition) as component,
96
- * instead it creates a new object and merges the definition from the `.vue` file in:
97
- * https://github.com/getkirby/kirby/blob/main/panel/public/js/plugins.js#L22-L25
98
- * After changes, the runtime updates the definition and re-renders the instances, but since
99
- * they are derived from the object created by Kirby, not the stored definition, nothing happens.
100
- * To fix that, we wrap `rerender()` and `reload()` so that before applying the updates, we first check
101
- * if the updated definition belongs to a component added by a Kirby plugin. To do so, we can check
102
- * whether the `__file` (added by plugin-vue2) or `__hmrId` (added by plugin-vue2-jsx) properties of the
103
- * updated module and the plugin component match. If so, we look up the component definition that is
104
- * _actually_ used by component instances rendered on the page (`window.panel.app.$options.components`)
105
- * and if it differs from the one stored in the HMR runtime's map, we updates the map's reference.
106
- *
107
- * We also check the component name and add a `$_isSection` flag if it's `k-something-section`, because
108
- * section components are hard to detect and need special treatment in `$_applyKirbyModifications`.
109
- *
110
- * `$_applyKirbyModifications`:
111
- *
112
- * Kirby modifies component definitions before registering components.
113
- * This includes adding the section mixin to section components,
114
- * giving templates priority over render functions if both exist
115
- * and resolving component names in `extends` to their definition object:
116
- * https://github.com/getkirby/kirby/blob/2965c3124e3b141072a2d46c798a327dda710060/panel/src/panel/plugins.js
117
- * When a module is reloaded, Vue receives a fresh component definition that is
118
- * missing these modifications. We need to re-apply them, else the runtime will
119
- * prune them when patching the stored definition to match the newer one.
120
- *
121
- * The call to `$_applyKirbyModifications()` is injected into `__VUE_HMR_RUNTIME__.reload()`
122
- * at the appropriate position using a RegExp in the Vite plugin's transform method.
123
- */
124
- const __INJECTED_HMR_CODE__ = `
125
- /** - injected by kirbyup - */
126
- for (const methodName of ['rerender', 'reload']) {
127
- const original = __VUE_HMR_RUNTIME__[methodName]
128
-
129
- __VUE_HMR_RUNTIME__[methodName] = function (id, updatedDef) {
130
- const key = updatedDef?.__file ? '__file' : updatedDef?.__hmrId ? '__hmrId' : null
131
-
132
- if (key) {
133
- const pluginComponents = window.panel.plugins.components
134
- // const usedComponentDefs = window.panel.app.$options.components
135
- const usedComponentDefs = window.panel.app._vnode.componentInstance.$options.components // #33
136
-
137
- for (const componentName in pluginComponents) {
138
- if (updatedDef[key] === pluginComponents[componentName][key]) {
139
- const usedDefinition = usedComponentDefs[componentName].options
140
-
141
- if (map[id].options !== usedDefinition)
142
- map[id].options = usedDefinition
143
-
144
- if (typeof map[id].options.$_isSection !== 'boolean')
145
- map[id].options.$_isSection = /^k-.*-section$/.test(componentName)
146
-
147
- break
148
- }
149
- }
150
- }
151
-
152
- return original.apply(this, arguments)
153
- }
154
- }
155
-
156
- function $_applyKirbyModifications(activeDef, newDef) {
157
- const usedComponentDefs = window.panel.app.$options.components
158
-
159
- if (newDef.template)
160
- newDef.render = null
161
-
162
- if (activeDef.$_isSection)
163
- newDef.$_isSection = true
164
- if (newDef.$_isSection && !newDef.mixins?.[0]?.methods?.load)
165
- newDef.mixins = [activeDef.mixins[0], ...(newDef.mixins || [])]
166
-
167
- if (typeof newDef.extends === 'string') {
168
- if (newDef.extends === activeDef.extends?.options?.name) {
169
- newDef.extends = activeDef.extends
170
- }
171
- else if (usedComponentDefs[newDef.extends]) {
172
- newDef.extends = usedComponentDefs[newDef.extends].extend({
173
- options: newDef,
174
- components: { ...usedComponentDefs, ...(newDef.components || {}) },
175
- })
176
- }
177
- else { newDef.extends = null }
178
- }
179
- }
180
- /** -- */
181
- `;
182
-
183
- //#endregion
184
- //#region src/node/plugins/hmr.ts
185
- function kirbyupHmrPlugin(options) {
186
- let config;
187
- let entry;
188
- let devIndexPath;
189
- return {
190
- name: "kirbyup:hmr",
191
- apply: "serve",
192
- configResolved(resolvedConfig) {
193
- config = resolvedConfig;
194
- entry = resolve(config.root, options.entry);
195
- devIndexPath = resolve(config.root, options.outDir || "", "index.dev.mjs");
196
- },
197
- transform(code, id) {
198
- if (isHmrRuntimeId(id)) return code.replace(/^.*=\s*record\.Ctor\.super\.extend\(options\)/m, "$_applyKirbyModifications(record.Ctor.options, options) // injected by kirbyup\n$&") + __INJECTED_HMR_CODE__;
199
- },
200
- configureServer(server) {
201
- if (!server.httpServer) return;
202
- server.httpServer.once("listening", async () => {
203
- const entryPath = entry.replace(`${config.root}/`, "");
204
- const { address, family, port } = server.httpServer.address();
205
- const baseUrl = `http://${family === "IPv6" ? `[${address}]` : address}:${port}${config.base}`;
206
- const entryUrl = new URL(entryPath, baseUrl).href;
207
- const pm = await detectPackageManager(config.root);
208
- await fsp.writeFile(devIndexPath, getViteProxyModule(entryUrl, pm));
209
- });
210
- },
211
- closeBundle() {
212
- if (fs.existsSync(devIndexPath)) fs.unlinkSync(devIndexPath);
213
- }
214
- };
215
- }
216
- /**
217
- * Proxy the JS file to "forward" the plugin script loaded by Kirby to the Vite server
218
- */
219
- function getViteProxyModule(entryUrl, packageManager) {
220
- const pm = packageManager?.name || "npm";
221
- return `
222
- try {
223
- await import("${entryUrl}");
224
- } catch (err) {
225
- console.error(
226
- "[kirbyup] Couldn't connect to the development server. Run \`${pm} run serve\` to start Vite or build the plugin with \`${pm} run build\` so Kirby uses the production version."
227
- );
228
- throw err;
229
- }
230
- `.trimStart();
231
- }
232
-
233
- //#endregion
234
- //#region src/node/plugins/vite-running.ts
235
- const VITE_RUNNING_FILENAME = ".vite-running";
236
- function kirbyupRunningMarkerPlugin(options = {}) {
237
- let config;
238
- let pluginDir;
239
- let markerPath;
240
- let exitCleanupRegistered = false;
241
- const ensureMarker = async () => {
242
- if (markerPath) return;
243
- try {
244
- const resolvedPath = await ensureViteRunningMarker(pluginDir);
245
- if (resolvedPath) markerPath = resolvedPath;
246
- } catch (error) {
247
- config.logger.warn(`[kirbyup] Failed to write ${VITE_RUNNING_FILENAME}: ${error.message}`);
248
- }
249
- };
250
- const cleanupMarker = async () => {
251
- if (!markerPath) return;
252
- try {
253
- await removeViteRunningMarker(markerPath);
254
- } catch (error) {
255
- config.logger.warn(`[kirbyup] Failed to remove ${VITE_RUNNING_FILENAME}: ${error.message}`);
256
- } finally {
257
- markerPath = void 0;
258
- }
259
- };
260
- const registerProcessCleanup = () => {
261
- if (exitCleanupRegistered) return;
262
- exitCleanupRegistered = true;
263
- process.once("exit", () => {
264
- if (markerPath && fs.existsSync(markerPath)) try {
265
- fs.rmSync(markerPath, { force: true });
266
- } catch {}
267
- });
268
- };
269
- return {
270
- name: "kirbyup:vite-running",
271
- configResolved(resolved) {
272
- config = resolved;
273
- pluginDir = resolve(resolved.root, options.outDir || "");
274
- registerProcessCleanup();
275
- },
276
- configureServer(server) {
277
- server.httpServer?.once("listening", () => ensureMarker());
278
- server.httpServer?.once("close", () => cleanupMarker());
279
- },
280
- buildStart() {
281
- if (config.command !== "build" || !config.build.watch) return;
282
- ensureMarker();
283
- }
284
- };
285
- }
286
- /**
287
- * Creates the `.vite-running` marker file if the directory is inside `site/plugins/`.
288
- * Returns `undefined` if the directory is not a Kirby plugin directory.
289
- */
290
- async function ensureViteRunningMarker(targetDir) {
291
- if (!isInsideKirbyPlugins(targetDir)) return;
292
- const markerPath = resolve(targetDir, VITE_RUNNING_FILENAME);
293
- await fsp.writeFile(markerPath, "", "utf8");
294
- return markerPath;
295
- }
296
- /**
297
- * Removes the `.vite-running` marker file.
298
- */
299
- async function removeViteRunningMarker(markerPath) {
300
- if (!markerPath) return;
301
- await fsp.rm(markerPath, { force: true });
302
- }
303
- /**
304
- * Checks if a directory is inside a Kirby plugin directory (`site/plugins/*`).
305
- * This is used to determine whether to create the `.vite-running` marker file,
306
- * which Kirby 6+ uses to detect development mode and load the development
307
- * build of Vue instead of the production build.
308
- */
309
- function isInsideKirbyPlugins(targetDir) {
310
- const initialDir = normalize(targetDir);
311
- let currentDir = initialDir;
312
- while (true) {
313
- const parentDir = dirname(currentDir);
314
- if (currentDir !== initialDir && isPathSegmentEqual(basename(currentDir), "plugins") && isPathSegmentEqual(basename(parentDir), "site")) return true;
315
- if (currentDir === parentDir) return false;
316
- currentDir = parentDir;
317
- }
318
- }
319
- /**
320
- * Performs case-insensitive comparison of path segments.
321
- */
322
- function isPathSegmentEqual(segment, expected) {
323
- return segment.toLowerCase() === expected;
324
- }
325
-
326
- //#endregion
327
- //#region src/node/utils.ts
328
- const compress = promisify(gzip);
329
- function toArray(array) {
330
- array ??= [];
331
- return Array.isArray(array) ? array : [array];
332
- }
333
- async function getCompressedSize(code) {
334
- return ` / gzip: ${((await compress(typeof code === "string" ? code : Buffer.from(code))).length / 1024).toFixed(2)} KiB`;
335
- }
336
- async function printFileInfo({ root, outDir, filePath, content, type, maxLength }) {
337
- const prettyOutDir = `${normalize(relative(root, resolve(root, outDir)))}/`;
338
- const kibs = content.length / 1024;
339
- const compressedSize = await getCompressedSize(content);
340
- const writeColor = type === "chunk" ? colors.cyan : colors.magenta;
341
- consola.log(colors.white(colors.dim(prettyOutDir)) + writeColor(filePath.padEnd(maxLength + 2)) + colors.dim(`${kibs.toFixed(2)} kB${compressedSize}`));
342
- }
343
-
344
- //#endregion
345
- //#region src/node/index.ts
346
- let resolvedKirbyupConfig;
347
- let resolvedPostCssConfig;
348
- const logLevel = "warn";
349
- const logger = createLogger(logLevel);
350
- const loggerWarn = logger.warn;
351
- logger.warn = (msg, options) => {
352
- if (msg.includes("(!) build.outDir")) return;
353
- loggerWarn(msg, options);
354
- };
355
- function getViteConfig(command, options) {
356
- const aliasDir = resolve(options.cwd, dirname(options.entry));
357
- const { alias = {}, vite } = resolvedKirbyupConfig;
358
- const userConfig = vite ?? {};
359
- const sharedConfig = {
360
- resolve: { alias: {
361
- "~/": `${aliasDir}/`,
362
- "@/": `${aliasDir}/`,
363
- ...alias
364
- } },
365
- plugins: [vuePlugin({ compiler: vueCompilerSfc }), vueJsxPlugin()],
366
- build: { copyPublicDir: false },
367
- ...resolvedPostCssConfig && { css: { postcss: resolvedPostCssConfig } },
368
- envDir: options.cwd,
369
- envPrefix: ["VITE_", "KIRBYUP_"],
370
- customLogger: logger,
371
- logLevel
372
- };
373
- if (command === "serve") {
374
- const { port, watch } = options;
375
- return mergeConfig(mergeConfig(sharedConfig, {
376
- plugins: [
377
- kirbyupHmrPlugin(options),
378
- kirbyupRunningMarkerPlugin({ outDir: options.outDir }),
379
- watch && fullReloadPlugin(watch)
380
- ].filter(Boolean),
381
- build: { rollupOptions: { input: resolve(options.cwd, options.entry) } },
382
- server: {
383
- port,
384
- strictPort: true,
385
- origin: `http://localhost:${port}`
386
- }
387
- }), userConfig);
388
- }
389
- const mode = options.watch ? "development" : "production";
390
- return mergeConfig(mergeConfig(sharedConfig, {
391
- mode,
392
- plugins: [kirbyupBuildCleanupPlugin(options)],
393
- build: {
394
- lib: {
395
- entry: resolve(options.cwd, options.entry),
396
- formats: ["es"],
397
- fileName: () => "index.js"
398
- },
399
- minify: mode === "production",
400
- outDir: options.outDir,
401
- emptyOutDir: false,
402
- rollupOptions: {
403
- external: ["vue"],
404
- output: { assetFileNames: "index.[ext]" }
405
- }
406
- }
407
- }), userConfig);
408
- }
409
- async function generate(options) {
410
- const config = getViteConfig("build", options);
411
- let result;
412
- try {
413
- result = await build(config);
414
- } catch (error) {
415
- consola.error("Build failed");
416
- if (config.mode === "production") throw error;
417
- }
418
- if (result && !options.watch) {
419
- const { output } = toArray(result)[0];
420
- let maxLength = 0;
421
- for (const chunkFile in output) {
422
- const fileNameLength = output[chunkFile].fileName.length;
423
- if (fileNameLength > maxLength) maxLength = fileNameLength;
424
- }
425
- for (const { fileName, type, code } of output) {
426
- const content = code || await fsp.readFile(resolve(options.outDir, fileName), "utf8");
427
- await printFileInfo({
428
- root: options.cwd,
429
- outDir: options.outDir,
430
- filePath: fileName,
431
- content,
432
- type,
433
- maxLength
434
- });
435
- }
436
- }
437
- return result;
438
- }
439
- async function build$1(options) {
440
- assertEntryExists(options);
441
- const { cwd } = options;
442
- const { config, configFile } = await loadConfig$1(cwd);
443
- resolvedKirbyupConfig = config ?? {};
444
- resolvedPostCssConfig = await resolvePostCSSConfig(cwd);
445
- consola.log(colors.green(`${name} v${version}`));
446
- consola.start(`Building ${colors.cyan(options.entry)}`);
447
- if (options.watch) consola.info("Running in watch mode");
448
- const debouncedBuild = debounce(async () => {
449
- generate(options).catch(handleError);
450
- }, 100);
451
- const startWatcher = async () => {
452
- if (!options.watch) return;
453
- const { watch } = await import("chokidar");
454
- const ignored = ["**/{.git,node_modules}/**", "index.{css,js,mjs}"];
455
- const watchPaths = typeof options.watch === "boolean" ? dirname(options.entry) : Array.isArray(options.watch) ? options.watch.filter((path) => typeof path === "string") : options.watch;
456
- consola.info(`Watching for changes in ${toArray(watchPaths).map((i) => colors.cyan(i)).join(", ")}`);
457
- const watcher = watch(watchPaths, {
458
- ignoreInitial: true,
459
- ignorePermissionErrors: true,
460
- ignored,
461
- cwd
462
- });
463
- if (configFile) watcher.add(configFile);
464
- watcher.on("all", async (type, file) => {
465
- if (configFile === resolve(cwd, file)) {
466
- resolvedKirbyupConfig = (await loadConfig$1(cwd)).config ?? {};
467
- consola.info(`${colors.cyan(basename(file))} changed, setting new config`);
468
- } else consola.log(`${colors.green(type)} ${colors.white(colors.dim(file))}`);
469
- debouncedBuild();
470
- });
471
- };
472
- await generate(options);
473
- consola.success("Build successful");
474
- startWatcher();
475
- }
476
- async function serve(options) {
477
- assertEntryExists(options);
478
- const { cwd } = options;
479
- const { config } = await loadConfig$1(cwd);
480
- resolvedKirbyupConfig = config ?? {};
481
- resolvedPostCssConfig = await resolvePostCSSConfig(cwd);
482
- consola.log(colors.green(`${name} v${version}`));
483
- consola.info(`Development server unavailable. Use watch mode for now: ${colors.cyan(`kirbyup build ${options.entry} --watch`)}`);
484
- throw new PrettyError("HMR is not yet implemented for Kirby 6 plugins. Please use watch mode instead.");
485
- }
486
- function assertEntryExists(options) {
487
- if (!fs.existsSync(resolve(options.cwd, options.entry))) throw new PrettyError(`Cannot find "${options.entry}"`);
488
- }
489
-
490
- //#endregion
491
- export { version as a, name as i, serve as n, handleError as r, build$1 as t };