@lukoweb/apitogo 0.2.51 → 0.2.53

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.
@@ -1,131 +1,192 @@
1
- import path from "node:path";
2
- import { normalizePath, type Plugin, type ResolvedConfig } from "vite";
3
- import { getZudokuRootDir } from "../cli/common/package-json.js";
4
- import { getCurrentConfig, loadZudokuConfig } from "../config/loader.js";
5
- import type { ConfigWithMeta } from "../config/loader.js";
6
-
7
- const virtualModuleId = "virtual:zudoku-config";
8
- const resolvedVirtualModuleId = `\0${virtualModuleId}`;
9
- const browserConfigLoaderPath = path.join(
10
- getZudokuRootDir(),
11
- "src/config/apitogo-config-loader.browser.ts",
12
- );
13
-
14
- function serializeClientConfig(config: ConfigWithMeta): string {
15
- const {
16
- plugins: _plugins,
17
- __meta: _meta,
18
- __resolvedModules,
19
- ...rest
20
- } = config;
21
-
22
- return JSON.stringify({ ...rest, __resolvedModules }, (_key, value) =>
23
- typeof value === "function" ? undefined : value,
24
- );
25
- }
26
-
27
- const viteConfigPlugin = (): Plugin => {
28
- let viteConfig: ResolvedConfig;
29
-
30
- return {
31
- name: "zudoku-config-plugin",
32
- resolveId(source, _importer, options) {
33
- if (source === virtualModuleId) {
34
- return resolvedVirtualModuleId;
35
- }
36
-
37
- if (!source.includes("apitogo-config-loader.node")) {
38
- return;
39
- }
40
-
41
- const isServerEnvironment =
42
- options?.ssr === true ||
43
- this.environment?.config?.consumer === "server";
44
-
45
- if (!isServerEnvironment) {
46
- return browserConfigLoaderPath;
47
- }
48
- },
49
- configResolved(resolvedConfig) {
50
- viteConfig = resolvedConfig;
51
- },
52
- async load(id) {
53
- if (id !== resolvedVirtualModuleId) return;
54
-
55
- const configPath = getCurrentConfig().__meta.configPath;
56
- if (!configPath) {
57
- return `export default {};`;
58
- }
59
-
60
- const { config: loadedConfig } = await loadZudokuConfig(
61
- {
62
- mode: viteConfig.mode,
63
- command: viteConfig.command,
64
- },
65
- viteConfig.root,
66
- );
67
- const clientMeta = {
68
- rootDir: loadedConfig.__meta.rootDir,
69
- pricingEnabled: loadedConfig.__meta.pricingEnabled,
70
- authenticationEnabled: loadedConfig.__meta.authenticationEnabled,
71
- previewPlans: loadedConfig.__meta.previewPlans,
72
- gatewayBaseUrl: loadedConfig.__meta.gatewayBaseUrl,
73
- };
74
-
75
- return `
76
- import rawConfig from "${normalizePath(configPath)}";
77
- import { runPluginTransformConfig } from "@lukoweb/apitogo/plugins";
78
-
79
- const embeddedConfig = ${serializeClientConfig(loadedConfig)};
80
-
81
- // JSON embedding drops Symbol values (React element's $$typeof). Prefer live
82
- // navigation/plugins from the TSX module so custom-page elements stay valid.
83
- const config = await runPluginTransformConfig({
84
- ...embeddedConfig,
85
- navigation: rawConfig?.navigation ?? embeddedConfig.navigation,
86
- plugins: rawConfig?.plugins ?? embeddedConfig.plugins ?? [],
87
- __meta: ${JSON.stringify(clientMeta)},
88
- });
89
- export default config;
90
- `;
91
- },
92
- async transform(code, id) {
93
- if (id !== getCurrentConfig().__meta.configPath) return;
94
-
95
- const replacedCode = code.replaceAll(
96
- /process\.env\.([a-z_][a-z0-9_]*)/gi,
97
- (_, envVar) => {
98
- const allowedPrefixes = Array.isArray(viteConfig.envPrefix)
99
- ? viteConfig.envPrefix
100
- : [viteConfig.envPrefix];
101
-
102
- if (!allowedPrefixes.some((prefix) => envVar.startsWith(prefix))) {
103
- viteConfig.logger.warn(
104
- `Warning: process.env.${envVar} is not prefixed with ${allowedPrefixes.join(
105
- " or ",
106
- )}.`,
107
- );
108
- return "undefined";
109
- }
110
-
111
- const value =
112
- viteConfig.define?.[`process.env.${envVar}`] ??
113
- viteConfig.define?.[`import.meta.env.${envVar}`];
114
-
115
- if (value === undefined) {
116
- viteConfig.logger.warn(
117
- `Warning: process.env.${envVar} is not defined.`,
118
- );
119
- return "undefined";
120
- }
121
-
122
- return value;
123
- },
124
- );
125
-
126
- return { code: replacedCode, map: null };
127
- },
128
- };
129
- };
130
-
131
- export default viteConfigPlugin;
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { normalizePath, type Plugin, type ResolvedConfig } from "vite";
4
+ import { getZudokuRootDir } from "../cli/common/package-json.js";
5
+ import { getCurrentConfig, loadZudokuConfig } from "../config/loader.js";
6
+ import type { ConfigWithMeta } from "../config/loader.js";
7
+
8
+ const virtualModuleId = "virtual:zudoku-config";
9
+ const resolvedVirtualModuleId = `\0${virtualModuleId}`;
10
+ const browserConfigLoaderPath = path.join(
11
+ getZudokuRootDir(),
12
+ "src/config/apitogo-config-loader.browser.ts",
13
+ );
14
+
15
+ /**
16
+ * Module settings that name a file on the build machine (`modules.landing.page`, `userPanel.dashboard.page`, …). The
17
+ * build imports those files itself (plugin-modules), so the browser never needs the path (API-127).
18
+ */
19
+ const BUILD_ONLY_KEYS = new Set(["page"]);
20
+
21
+ /** Drops the build-only file paths from the module settings. */
22
+ export function stripBuildOnlyPaths(value: unknown): unknown {
23
+ if (Array.isArray(value)) return value.map(stripBuildOnlyPaths);
24
+ if (typeof value !== "object" || value === null) return value;
25
+ return Object.fromEntries(
26
+ Object.entries(value).flatMap(([key, child]) =>
27
+ BUILD_ONLY_KEYS.has(key) && typeof child === "string"
28
+ ? []
29
+ : [[key, stripBuildOnlyPaths(child)]],
30
+ ),
31
+ );
32
+ }
33
+
34
+ /**
35
+ * The config the browser gets. Plugins and functions cannot be embedded, `__meta` is replaced by a client subset,
36
+ * and no absolute path of the build machine is left in it (API-127).
37
+ */
38
+ export function serializeClientConfig(config: ConfigWithMeta): string {
39
+ const {
40
+ plugins: _plugins,
41
+ __meta: _meta,
42
+ // Folders of the plugin packages on the build machine, for the build's Tailwind sources only.
43
+ __pluginDirs: _pluginDirs,
44
+ __resolvedModules,
45
+ modules,
46
+ ...rest
47
+ } = config;
48
+
49
+ return JSON.stringify(
50
+ {
51
+ ...rest,
52
+ ...(modules !== undefined
53
+ ? { modules: stripBuildOnlyPaths(modules) }
54
+ : {}),
55
+ __resolvedModules,
56
+ },
57
+ (_key, value) => (typeof value === "function" ? undefined : value),
58
+ );
59
+ }
60
+
61
+ /** The `__meta` the browser gets: no build-machine paths such as `rootDir` or `configPath` (API-127). */
62
+ export function clientMetaOf(meta: ConfigWithMeta["__meta"]) {
63
+ return {
64
+ pricingEnabled: meta.pricingEnabled,
65
+ authenticationEnabled: meta.authenticationEnabled,
66
+ previewPlans: meta.previewPlans,
67
+ gatewayBaseUrl: meta.gatewayBaseUrl,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * The navigation as the config file states it, for the browser. A TSX/TS config is imported, so its React elements
73
+ * stay live. A JSON config holds no elements, so only its navigation is embedded: importing the file would ship the
74
+ * whole file, file paths included (API-127).
75
+ */
76
+ async function rawConfigSource(configPath: string): Promise<string> {
77
+ if (!configPath.toLowerCase().endsWith(".json"))
78
+ return `import rawConfig from "${normalizePath(configPath)}";`;
79
+ let navigation: unknown;
80
+ try {
81
+ navigation = (
82
+ JSON.parse(await readFile(configPath, "utf8")) as {
83
+ navigation?: unknown;
84
+ }
85
+ ).navigation;
86
+ } catch {
87
+ navigation = undefined;
88
+ }
89
+ return `const rawConfig = ${JSON.stringify(navigation === undefined ? {} : { navigation })};`;
90
+ }
91
+
92
+ const viteConfigPlugin = (): Plugin => {
93
+ let viteConfig: ResolvedConfig;
94
+
95
+ return {
96
+ name: "zudoku-config-plugin",
97
+ resolveId(source, _importer, options) {
98
+ if (source === virtualModuleId) {
99
+ return resolvedVirtualModuleId;
100
+ }
101
+
102
+ if (!source.includes("apitogo-config-loader.node")) {
103
+ return;
104
+ }
105
+
106
+ const isServerEnvironment =
107
+ options?.ssr === true ||
108
+ this.environment?.config?.consumer === "server";
109
+
110
+ if (!isServerEnvironment) {
111
+ return browserConfigLoaderPath;
112
+ }
113
+ },
114
+ configResolved(resolvedConfig) {
115
+ viteConfig = resolvedConfig;
116
+ },
117
+ async load(id) {
118
+ if (id !== resolvedVirtualModuleId) return;
119
+
120
+ const configPath = getCurrentConfig().__meta.configPath;
121
+ if (!configPath) {
122
+ return `export default {};`;
123
+ }
124
+
125
+ const { config: loadedConfig } = await loadZudokuConfig(
126
+ {
127
+ mode: viteConfig.mode,
128
+ command: viteConfig.command,
129
+ },
130
+ viteConfig.root,
131
+ );
132
+ const clientMeta = clientMetaOf(loadedConfig.__meta);
133
+ // A JSON config is no longer imported, so watch it here to keep reloading on edits.
134
+ this.addWatchFile(configPath);
135
+
136
+ return `
137
+ ${await rawConfigSource(configPath)}
138
+ import { runPluginTransformConfig } from "@lukoweb/apitogo/plugins";
139
+
140
+ const embeddedConfig = ${serializeClientConfig(loadedConfig)};
141
+
142
+ // JSON embedding drops Symbol values (React element's $$typeof). Prefer live
143
+ // navigation/plugins from the TSX module so custom-page elements stay valid.
144
+ const config = await runPluginTransformConfig({
145
+ ...embeddedConfig,
146
+ navigation: rawConfig?.navigation ?? embeddedConfig.navigation,
147
+ plugins: rawConfig?.plugins ?? embeddedConfig.plugins ?? [],
148
+ __meta: ${JSON.stringify(clientMeta)},
149
+ });
150
+ export default config;
151
+ `;
152
+ },
153
+ async transform(code, id) {
154
+ if (id !== getCurrentConfig().__meta.configPath) return;
155
+
156
+ const replacedCode = code.replaceAll(
157
+ /process\.env\.([a-z_][a-z0-9_]*)/gi,
158
+ (_, envVar) => {
159
+ const allowedPrefixes = Array.isArray(viteConfig.envPrefix)
160
+ ? viteConfig.envPrefix
161
+ : [viteConfig.envPrefix];
162
+
163
+ if (!allowedPrefixes.some((prefix) => envVar.startsWith(prefix))) {
164
+ viteConfig.logger.warn(
165
+ `Warning: process.env.${envVar} is not prefixed with ${allowedPrefixes.join(
166
+ " or ",
167
+ )}.`,
168
+ );
169
+ return "undefined";
170
+ }
171
+
172
+ const value =
173
+ viteConfig.define?.[`process.env.${envVar}`] ??
174
+ viteConfig.define?.[`import.meta.env.${envVar}`];
175
+
176
+ if (value === undefined) {
177
+ viteConfig.logger.warn(
178
+ `Warning: process.env.${envVar} is not defined.`,
179
+ );
180
+ return "undefined";
181
+ }
182
+
183
+ return value;
184
+ },
185
+ );
186
+
187
+ return { code: replacedCode, map: null };
188
+ },
189
+ };
190
+ };
191
+
192
+ export default viteConfigPlugin;