@stacksjs/stx 0.2.120 → 0.2.122

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/index.d.ts CHANGED
@@ -54,6 +54,7 @@ export * from './a11y';
54
54
  export * from './analytics';
55
55
  export * from './analyzer';
56
56
  export * from './animation';
57
+ export * from './appearance-bootstrap';
57
58
  export * from './assets';
58
59
  export * from './app-shell';
59
60
  export * from './async-components';
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export * from "./a11y.js";
13
13
  export * from "./analytics.js";
14
14
  export * from "./analyzer.js";
15
15
  export * from "./animation.js";
16
+ export * from "./appearance-bootstrap.js";
16
17
  export * from "./assets.js";
17
18
  export * from "./app-shell.js";
18
19
  export * from "./async-components.js";
@@ -62,10 +62,16 @@ export declare interface NativeBuildConfig {
62
62
  }
63
63
  }
64
64
  systemTray?: boolean
65
+ menubarOnly?: boolean
66
+ infoPlist?: Record<string, string | number | boolean>
67
+ minimumSystemVersion?: string
68
+ category?: string
69
+ copyright?: string
65
70
  hotReload?: boolean
66
71
  devTools?: boolean
67
72
  craftPath?: string
68
73
  env?: Record<string, string>
74
+ context?: Record<string, unknown>
69
75
  verbose?: boolean
70
76
  }
71
77
  export declare interface NativeBuildResult {
@@ -4,6 +4,7 @@ import { spawn, execSync } from "node:child_process";
4
4
  import { tmpdir, homedir, platform as osPlatform } from "node:os";
5
5
  import { injectCraftBridge } from "./craft-bridge.js";
6
6
  import { processCraftComponents, CRAFT_COMPONENT_STYLES } from "./craft-components.js";
7
+ import { renderTemplate } from "./render.js";
7
8
  export async function buildNative(config) {
8
9
  const startTime = Date.now();
9
10
  try {
@@ -17,7 +18,10 @@ export async function buildNative(config) {
17
18
  const target = config.target || detectPlatform(), format = config.format || getDefaultFormat(target), outputDir = config.output || join(process.cwd(), "dist", "native");
18
19
  if (!existsSync(outputDir))
19
20
  mkdirSync(outputDir, { recursive: !0 });
20
- let html = await Bun.file(config.input).text();
21
+ let html = await renderTemplate(config.input, {
22
+ context: config.context,
23
+ options: { isDev: !1 }
24
+ });
21
25
  html = injectCraftBridge(html, { debug: config.verbose });
22
26
  html = processCraftComponents(html);
23
27
  if (!html.includes("craft-component-styles")) {
@@ -87,64 +91,51 @@ async function buildMacOS(html, appName, appVersion, outputDir, format, config,
87
91
  sidebarConfigPath = join(resourcesDir, "sidebar-config.json");
88
92
  await Bun.write(sidebarConfigPath, JSON.stringify(config.sidebar.config));
89
93
  }
90
- const sidebarFlags = config.sidebar?.enabled ? `--native-sidebar --sidebar-width ${config.sidebar?.width || 240} --sidebar-config "$(cat "$RESOURCES/sidebar-config.json")"` : "", launcherScript = `#!/bin/bash
94
+ const sidebarFlags = config.sidebar?.enabled ? `--native-sidebar --sidebar-width ${config.sidebar?.width || 240} --sidebar-config "$(cat "$RESOURCES/sidebar-config.json")"` : "", craftBinaryDest = join(macOSDir, "craft");
95
+ copyFileSync(craftPath, craftBinaryDest);
96
+ chmodSync(craftBinaryDest, 493);
97
+ const menubarOnly = config.menubarOnly ?? config.systemTray, launcherScript = `#!/bin/bash
98
+ set -euo pipefail
91
99
  DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
92
100
  RESOURCES="$DIR/../Resources"
93
- "${craftPath}" --html "$(cat "$RESOURCES/index.html")" \\
94
- --title "${config.window?.title || appName}" \\
95
- --width ${config.window?.width || 1200} \\
96
- --height ${config.window?.height || 800} \\
97
- ${config.systemTray ? "--system-tray" : ""} \\
98
- ${config.devTools ? "--dev-tools" : ""} \\
99
- ${config.window?.frameless ? "--frameless" : ""} \\
100
- ${config.window?.alwaysOnTop ? "--always-on-top" : ""} \\
101
- ${sidebarFlags}
101
+ exec "$DIR/craft" --html-file "$RESOURCES/index.html" \\
102
+ ${[
103
+ `--title "${config.window?.title || appName}"`,
104
+ `--width ${config.window?.width || 1200}`,
105
+ `--height ${config.window?.height || 800}`,
106
+ menubarOnly ? "--menubar-only" : config.systemTray ? "--system-tray" : "",
107
+ config.devTools ? "" : "--no-devtools",
108
+ config.window?.frameless ? "--frameless" : "",
109
+ config.window?.transparent ? "--transparent" : "",
110
+ config.window?.alwaysOnTop ? "--always-on-top" : "",
111
+ config.window?.resizable === !1 ? "--no-resize" : "",
112
+ sidebarFlags
113
+ ].filter(Boolean).join(" \\\n ")}
102
114
  `, launcherPath = join(macOSDir, appName);
103
115
  await Bun.write(launcherPath, launcherScript);
104
116
  chmodSync(launcherPath, 493);
105
- const craftBinaryDest = join(macOSDir, "craft");
106
- try {
107
- copyFileSync(craftPath, craftBinaryDest);
108
- chmodSync(craftBinaryDest, 493);
109
- } catch {
110
- const craftWrapperScript = `#!/bin/bash
111
- exec "${craftPath}" "$@"`;
112
- await Bun.write(craftBinaryDest, craftWrapperScript);
113
- chmodSync(craftBinaryDest, 493);
114
- }
115
- const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
116
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
117
- <plist version="1.0">
118
- <dict>
119
- <key>CFBundleExecutable</key>
120
- <string>${appName}</string>
121
- <key>CFBundleIdentifier</key>
122
- <string>${bundleId}</string>
123
- <key>CFBundleName</key>
124
- <string>${appName}</string>
125
- <key>CFBundleDisplayName</key>
126
- <string>${appName}</string>
127
- <key>CFBundleShortVersionString</key>
128
- <string>${appVersion}</string>
129
- <key>CFBundleVersion</key>
130
- <string>${appVersion}</string>
131
- <key>CFBundlePackageType</key>
132
- <string>APPL</string>
133
- <key>CFBundleSignature</key>
134
- <string>????</string>
135
- <key>LSMinimumSystemVersion</key>
136
- <string>10.15</string>
137
- <key>NSHighResolutionCapable</key>
138
- <true/>
139
- <key>NSSupportsAutomaticGraphicsSwitching</key>
140
- <true/>
141
- <key>LSUIElement</key>
142
- <${config.systemTray ? "true" : "false"}/>
143
- </dict>
144
- </plist>`;
145
- await Bun.write(join(contentsDir, "Info.plist"), infoPlist);
146
- if (config.icon && await Bun.file(config.icon).exists())
117
+ const hasIcon = Boolean(config.icon && await Bun.file(config.icon).exists());
118
+ if (hasIcon)
147
119
  copyFileSync(config.icon, join(resourcesDir, "AppIcon.icns"));
120
+ const plistEntries = {
121
+ CFBundleExecutable: appName,
122
+ CFBundleIdentifier: bundleId,
123
+ CFBundleName: appName,
124
+ CFBundleDisplayName: appName,
125
+ CFBundleShortVersionString: appVersion,
126
+ CFBundleVersion: appVersion,
127
+ CFBundlePackageType: "APPL",
128
+ CFBundleSignature: "????",
129
+ LSMinimumSystemVersion: config.minimumSystemVersion || "13.0",
130
+ NSHighResolutionCapable: !0,
131
+ NSSupportsAutomaticGraphicsSwitching: !0,
132
+ LSUIElement: config.menubarOnly ?? Boolean(config.systemTray),
133
+ ...hasIcon ? { CFBundleIconFile: "AppIcon" } : {},
134
+ ...config.category ? { LSApplicationCategoryType: config.category } : {},
135
+ ...config.copyright ? { NSHumanReadableCopyright: config.copyright } : {},
136
+ ...config.infoPlist || {}
137
+ };
138
+ await Bun.write(join(contentsDir, "Info.plist"), renderInfoPlist(plistEntries));
148
139
  if (format === "dmg") {
149
140
  const dmgPath = join(outputDir, `${appName}-${appVersion}.dmg`), dmgResult = await createDMG(appBundlePath, dmgPath, appName);
150
141
  if (!dmgResult.success)
@@ -153,19 +144,38 @@ exec "${craftPath}" "$@"`;
153
144
  }
154
145
  return { success: !0, target: "macos", format: "app", outputPath: appBundlePath };
155
146
  }
147
+ function escapeXml(value) {
148
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
149
+ }
150
+ function renderInfoPlist(entries) {
151
+ return `<?xml version="1.0" encoding="UTF-8"?>
152
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
153
+ <plist version="1.0">
154
+ <dict>
155
+ ${Object.entries(entries).map(([key, value]) => {
156
+ const rendered = typeof value === "boolean" ? `<${value}/>` : typeof value === "number" ? `<integer>${value}</integer>` : `<string>${escapeXml(String(value))}</string>`;
157
+ return ` <key>${escapeXml(key)}</key>
158
+ ${rendered}`;
159
+ }).join(`
160
+ `)}
161
+ </dict>
162
+ </plist>
163
+ `;
164
+ }
156
165
  async function buildWindows(html, appName, appVersion, outputDir, format, config, craftPath) {
157
166
  const appDir = join(outputDir, appName);
158
167
  mkdirSync(appDir, { recursive: !0 });
159
168
  const htmlPath = join(appDir, "index.html");
160
169
  await Bun.write(htmlPath, html);
170
+ copyFileSync(craftPath, join(appDir, "craft.exe"));
161
171
  const launcherBat = `@echo off
162
172
  set DIR=%~dp0
163
- "${craftPath}" --html-file "%DIR%index.html" ^
173
+ "%DIR%craft.exe" --html-file "%DIR%index.html" ^
164
174
  --title "${config.window?.title || appName}" ^
165
175
  --width ${config.window?.width || 1200} ^
166
176
  --height ${config.window?.height || 800} ^
167
177
  ${config.systemTray ? "--system-tray" : ""} ^
168
- ${config.devTools ? "--dev-tools" : ""}
178
+ ${config.devTools ? "" : "--no-devtools"}
169
179
  `;
170
180
  await Bun.write(join(appDir, `${appName}.bat`), launcherBat);
171
181
  if (format === "zip") {
@@ -181,14 +191,18 @@ async function buildLinux(html, appName, appVersion, outputDir, format, config,
181
191
  mkdirSync(appDir, { recursive: !0 });
182
192
  const htmlPath = join(appDir, "index.html");
183
193
  await Bun.write(htmlPath, html);
194
+ const craftDest = join(appDir, "craft");
195
+ copyFileSync(craftPath, craftDest);
196
+ chmodSync(craftDest, 493);
184
197
  const launcherScript = `#!/bin/bash
198
+ set -euo pipefail
185
199
  DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
186
- "${craftPath}" --html-file "$DIR/index.html" \\
200
+ exec "$DIR/craft" --html-file "$DIR/index.html" \\
187
201
  --title "${config.window?.title || appName}" \\
188
202
  --width ${config.window?.width || 1200} \\
189
203
  --height ${config.window?.height || 800} \\
190
204
  ${config.systemTray ? "--system-tray" : ""} \\
191
- ${config.devTools ? "--dev-tools" : ""}
205
+ ${config.devTools ? "" : "--no-devtools"}
192
206
  `, launcherPath = join(appDir, appName.toLowerCase());
193
207
  await Bun.write(launcherPath, launcherScript);
194
208
  chmodSync(launcherPath, 493);
@@ -231,10 +245,13 @@ async function buildAndroid(_html, appName, _appVersion, _outputDir, _config, _c
231
245
  };
232
246
  }
233
247
  async function findCraftBinary() {
248
+ if (process.env.CRAFT_BIN && await Bun.file(process.env.CRAFT_BIN).exists())
249
+ return process.env.CRAFT_BIN;
234
250
  const possiblePaths = [
235
- join(process.cwd(), "../../craft/packages/zig/zig-out/bin/craft-minimal"),
236
- join(process.cwd(), "../craft/packages/zig/zig-out/bin/craft-minimal"),
237
- join(homedir(), "Code/craft/packages/zig/zig-out/bin/craft-minimal"),
251
+ join(process.cwd(), "../../craft/packages/zig/zig-out/bin/craft"),
252
+ join(process.cwd(), "../craft/packages/zig/zig-out/bin/craft"),
253
+ join(homedir(), "Code/Tools/craft/packages/zig/zig-out/bin/craft"),
254
+ join(homedir(), "Code/craft/packages/zig/zig-out/bin/craft"),
238
255
  "/usr/local/bin/craft",
239
256
  "/usr/bin/craft",
240
257
  join(homedir(), ".local/bin/craft"),
package/dist/process.js CHANGED
@@ -12,9 +12,10 @@ import { extractBridgeData, processClientScript } from "./client-script.js";
12
12
  import { processReactiveDirectives } from "./reactive.js";
13
13
  import { processMarkdownFileDirectives } from "./assets.js";
14
14
  import { processAuthDirectives, processConditionals, processEnvDirective, processIssetEmptyDirectives } from "./conditionals.js";
15
- import { injectCspMetaTag, processCspDirectives } from "./csp.js";
15
+ import { addNonceToInlineContent, injectCspMetaTag, processCspDirectives } from "./csp.js";
16
16
  import { processCsrfDirectives } from "./csrf.js";
17
17
  import { processCustomDirectives } from "./custom-directives.js";
18
+ import { processAppearanceBootstrapDirective } from "./appearance-bootstrap.js";
18
19
  import { devHelpers, errorLogger, errorRecovery, safeExecuteAsync, StxRuntimeError, StxValidationError } from "./error-handling.js";
19
20
  import { getHead as getHeadStatic } from "./head.js";
20
21
  import { processExpressions, usesSignalsInScript } from "./expressions.js";
@@ -669,6 +670,7 @@ async function processOtherDirectives(template, context, filePath, options, depe
669
670
  output = processXElementDirectives(output);
670
671
  const pageFilePath = context.__originalFilePath || filePath, serverData = extractBridgeData(context);
671
672
  output = await processSignals(output, opts, pageFilePath, serverData);
673
+ output = processAppearanceBootstrapDirective(output, context);
672
674
  output = await runPostProcessingMiddleware(output, context, filePath, opts);
673
675
  if (opts.media?.image?.autoSiblings) {
674
676
  const publicDir = opts.publicDir || "public", { rewriteImagesWithSiblings } = await import("./image-optimization/auto-sibling.js");
@@ -775,5 +777,7 @@ ${runtimeScript}`);
775
777
  }
776
778
  if (clientScriptsTransformed)
777
779
  context.__stx_event_bindings = [];
780
+ if (opts.csp?.enabled && opts.csp.useNonce && typeof context.cspNonce === "string")
781
+ output = addNonceToInlineContent(output, context.cspNonce);
778
782
  return output;
779
783
  }