create-prisma-php-app 4.4.7-beta → 4.4.7
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/bootstrap.php +560 -284
- package/dist/index.js +2 -2
- package/dist/prisma-php.js +2 -2
- package/dist/public/.htaccess +1 -1
- package/dist/public/assets/images/prisma-php.svg +146 -146
- package/dist/public/js/pp-reactive-v1.js +1 -1
- package/dist/settings/bs-config.ts +141 -16
- package/dist/settings/project-name.ts +50 -25
- package/dist/settings/vite-plugins/generate-global-types.ts +301 -0
- package/dist/src/Lib/Auth/Auth.php +92 -64
- package/dist/src/Lib/Auth/AuthConfig.php +1 -0
- package/dist/src/Lib/MCP/mcp-server.php +8 -7
- package/dist/src/Lib/Middleware/AuthMiddleware.php +31 -15
- package/dist/src/Lib/Middleware/CorsMiddleware.php +8 -6
- package/dist/src/Lib/Websocket/ConnectionManager.php +3 -3
- package/dist/src/Lib/Websocket/websocket-server.php +8 -7
- package/dist/src/app/index.php +2 -2
- package/dist/src/app/layout.php +1 -1
- package/dist/src/app/not-found.php +2 -2
- package/dist/tsconfig.json +1 -1
- package/dist/vite.config.ts +19 -2
- package/package.json +4 -4
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createProxyMiddleware,
|
|
3
|
+
responseInterceptor,
|
|
4
|
+
} from "http-proxy-middleware";
|
|
2
5
|
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
6
|
+
import { networkInterfaces } from "os";
|
|
3
7
|
import browserSync, { BrowserSyncInstance } from "browser-sync";
|
|
4
8
|
import prismaPhpConfigJson from "../prisma-php.json";
|
|
5
9
|
import { generateFileListJson } from "./files-list.js";
|
|
@@ -13,11 +17,24 @@ import {
|
|
|
13
17
|
} from "./class-imports";
|
|
14
18
|
import { checkComponentImports } from "./component-import-checker";
|
|
15
19
|
import { DebouncedWorker, createSrcWatcher, DEFAULT_AWF } from "./utils.js";
|
|
20
|
+
import chalk from "chalk";
|
|
16
21
|
|
|
17
22
|
const { __dirname } = getFileMeta();
|
|
18
23
|
const bs: BrowserSyncInstance = browserSync.create();
|
|
19
24
|
|
|
20
|
-
const PUBLIC_IGNORE_DIRS = [
|
|
25
|
+
const PUBLIC_IGNORE_DIRS = [""];
|
|
26
|
+
|
|
27
|
+
function getExternalIP(): string | null {
|
|
28
|
+
const nets = networkInterfaces();
|
|
29
|
+
for (const name of Object.keys(nets)) {
|
|
30
|
+
for (const net of nets[name]!) {
|
|
31
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
32
|
+
return net.address;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
21
38
|
|
|
22
39
|
const pipeline = new DebouncedWorker(
|
|
23
40
|
async () => {
|
|
@@ -46,18 +63,18 @@ const pipeline = new DebouncedWorker(
|
|
|
46
63
|
}
|
|
47
64
|
},
|
|
48
65
|
350,
|
|
49
|
-
"bs-pipeline"
|
|
66
|
+
"bs-pipeline",
|
|
50
67
|
);
|
|
51
68
|
|
|
52
69
|
const publicPipeline = new DebouncedWorker(
|
|
53
70
|
async () => {
|
|
54
|
-
console.log("→ Public directory changed, reloading browser...");
|
|
71
|
+
console.log(chalk.cyan("→ Public directory changed, reloading browser..."));
|
|
55
72
|
if (bs.active) {
|
|
56
73
|
bs.reload();
|
|
57
74
|
}
|
|
58
75
|
},
|
|
59
76
|
350,
|
|
60
|
-
"bs-public-pipeline"
|
|
77
|
+
"bs-public-pipeline",
|
|
61
78
|
);
|
|
62
79
|
|
|
63
80
|
createSrcWatcher(join(SRC_DIR, "**", "*"), {
|
|
@@ -90,16 +107,17 @@ createSrcWatcher(join(PUBLIC_DIR, "**", "*"), {
|
|
|
90
107
|
|
|
91
108
|
const viteFlagFile = join(__dirname, "..", ".pp", ".vite-build-complete");
|
|
92
109
|
mkdirSync(dirname(viteFlagFile), { recursive: true });
|
|
93
|
-
writeFileSync(viteFlagFile, "");
|
|
94
110
|
|
|
95
111
|
if (!existsSync(viteFlagFile)) {
|
|
96
112
|
writeFileSync(viteFlagFile, "0");
|
|
113
|
+
} else {
|
|
114
|
+
writeFileSync(viteFlagFile, "");
|
|
97
115
|
}
|
|
98
116
|
|
|
99
117
|
createSrcWatcher(viteFlagFile, {
|
|
100
118
|
onEvent: (ev) => {
|
|
101
119
|
if (ev === "change" && bs.active) {
|
|
102
|
-
console.log("→ Vite build complete, reloading browser...");
|
|
120
|
+
console.log(chalk.green("→ Vite build complete, reloading browser..."));
|
|
103
121
|
bs.reload();
|
|
104
122
|
}
|
|
105
123
|
},
|
|
@@ -112,6 +130,7 @@ createSrcWatcher(viteFlagFile, {
|
|
|
112
130
|
bs.init(
|
|
113
131
|
{
|
|
114
132
|
proxy: "http://localhost:3000",
|
|
133
|
+
online: true,
|
|
115
134
|
middleware: [
|
|
116
135
|
(_req, res, next) => {
|
|
117
136
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
@@ -119,36 +138,142 @@ bs.init(
|
|
|
119
138
|
res.setHeader("Expires", "0");
|
|
120
139
|
next();
|
|
121
140
|
},
|
|
141
|
+
|
|
142
|
+
(req, _, next) => {
|
|
143
|
+
const time = new Date().toLocaleTimeString();
|
|
144
|
+
console.log(
|
|
145
|
+
`${chalk.gray(time)} ${chalk.cyan("[Proxy]")} ${chalk.bold(req.method)} ${req.url}`,
|
|
146
|
+
);
|
|
147
|
+
next();
|
|
148
|
+
},
|
|
149
|
+
|
|
122
150
|
createProxyMiddleware({
|
|
123
151
|
target: prismaPhpConfigJson.bsTarget,
|
|
124
152
|
changeOrigin: true,
|
|
125
153
|
pathRewrite: {},
|
|
154
|
+
selfHandleResponse: true,
|
|
155
|
+
|
|
156
|
+
on: {
|
|
157
|
+
proxyReq: (proxyReq, req, _res) => {
|
|
158
|
+
proxyReq.setHeader("Accept-Encoding", "");
|
|
159
|
+
|
|
160
|
+
const sendsJson =
|
|
161
|
+
req.headers["content-type"]?.includes("application/json");
|
|
162
|
+
const asksJson =
|
|
163
|
+
req.headers["accept"]?.includes("application/json");
|
|
164
|
+
|
|
165
|
+
if (!sendsJson && !asksJson) return;
|
|
166
|
+
|
|
167
|
+
const originalWrite = proxyReq.write;
|
|
168
|
+
proxyReq.write = function (data, ...args) {
|
|
169
|
+
if (data) {
|
|
170
|
+
try {
|
|
171
|
+
const body = data.toString();
|
|
172
|
+
const json = JSON.parse(body);
|
|
173
|
+
console.log(
|
|
174
|
+
chalk.blue("→ API Request:"),
|
|
175
|
+
JSON.stringify(json, null, 2),
|
|
176
|
+
);
|
|
177
|
+
} catch {
|
|
178
|
+
if (data.toString().trim() !== "") {
|
|
179
|
+
console.log(chalk.blue("→ API Request:"), data.toString());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// @ts-ignore
|
|
184
|
+
return originalWrite.call(proxyReq, data, ...args);
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
proxyRes: responseInterceptor(
|
|
189
|
+
async (responseBuffer, proxyRes, _req, _res) => {
|
|
190
|
+
const contentType = proxyRes.headers["content-type"] || "";
|
|
191
|
+
|
|
192
|
+
if (!contentType.includes("application/json")) {
|
|
193
|
+
return responseBuffer;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const body = responseBuffer.toString("utf8");
|
|
198
|
+
console.log(
|
|
199
|
+
chalk.green("← API Response:"),
|
|
200
|
+
JSON.stringify(JSON.parse(body), null, 2),
|
|
201
|
+
);
|
|
202
|
+
console.log(
|
|
203
|
+
chalk.gray("----------------------------------------"),
|
|
204
|
+
);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.log(
|
|
207
|
+
chalk.red("← API Response (Parse Error):"),
|
|
208
|
+
responseBuffer.toString(),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return responseBuffer;
|
|
213
|
+
},
|
|
214
|
+
),
|
|
215
|
+
|
|
216
|
+
error: (err) => {
|
|
217
|
+
console.error(chalk.red("Proxy Error:"), err);
|
|
218
|
+
},
|
|
219
|
+
},
|
|
126
220
|
}),
|
|
127
221
|
],
|
|
128
|
-
|
|
129
222
|
notify: false,
|
|
130
223
|
open: false,
|
|
131
224
|
ghostMode: false,
|
|
132
225
|
codeSync: true,
|
|
226
|
+
logLevel: "silent",
|
|
133
227
|
},
|
|
134
228
|
(err, bsInstance) => {
|
|
135
229
|
if (err) {
|
|
136
|
-
console.error("BrowserSync failed to start:", err);
|
|
230
|
+
console.error(chalk.red("BrowserSync failed to start:"), err);
|
|
137
231
|
return;
|
|
138
232
|
}
|
|
139
233
|
|
|
234
|
+
const bsPort = bsInstance.getOption("port");
|
|
140
235
|
const urls = bsInstance.getOption("urls");
|
|
236
|
+
const localUrl = urls.get("local") || `http://localhost:${bsPort}`;
|
|
237
|
+
const externalIP = getExternalIP();
|
|
238
|
+
const externalUrl =
|
|
239
|
+
urls.get("external") ||
|
|
240
|
+
(externalIP ? `http://${externalIP}:${bsPort}` : null);
|
|
241
|
+
const uiUrl = urls.get("ui");
|
|
242
|
+
const uiExtUrl = urls.get("ui-external");
|
|
243
|
+
|
|
244
|
+
console.log("");
|
|
245
|
+
console.log(chalk.green.bold("✔ Ports Configured:"));
|
|
246
|
+
console.log(
|
|
247
|
+
` ${chalk.blue.bold("Frontend (BrowserSync):")} ${chalk.magenta(localUrl)}`,
|
|
248
|
+
);
|
|
249
|
+
console.log(
|
|
250
|
+
` ${chalk.yellow.bold("Backend (PHP Target):")} ${chalk.magenta(
|
|
251
|
+
prismaPhpConfigJson.bsTarget || "http://localhost:80",
|
|
252
|
+
)}`,
|
|
253
|
+
);
|
|
254
|
+
console.log(chalk.gray(" ------------------------------------"));
|
|
255
|
+
|
|
256
|
+
if (externalUrl) {
|
|
257
|
+
console.log(
|
|
258
|
+
` ${chalk.bold("External:")} ${chalk.magenta(externalUrl)}`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (uiUrl) {
|
|
263
|
+
console.log(` ${chalk.bold("UI:")} ${chalk.magenta(uiUrl)}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
141
266
|
const out = {
|
|
142
|
-
local:
|
|
143
|
-
external:
|
|
144
|
-
ui:
|
|
145
|
-
uiExternal:
|
|
267
|
+
local: localUrl,
|
|
268
|
+
external: externalUrl,
|
|
269
|
+
ui: uiUrl,
|
|
270
|
+
uiExternal: uiExtUrl,
|
|
146
271
|
};
|
|
147
272
|
|
|
148
273
|
writeFileSync(
|
|
149
274
|
join(__dirname, "bs-config.json"),
|
|
150
|
-
JSON.stringify(out, null, 2)
|
|
275
|
+
JSON.stringify(out, null, 2),
|
|
151
276
|
);
|
|
152
|
-
console.log("
|
|
153
|
-
}
|
|
277
|
+
console.log(`\n${chalk.gray("Press Ctrl+C to stop.")}\n`);
|
|
278
|
+
},
|
|
154
279
|
);
|
|
@@ -13,7 +13,7 @@ const newProjectName = basename(join(__dirname, ".."));
|
|
|
13
13
|
|
|
14
14
|
function updateProjectNameInConfig(
|
|
15
15
|
filePath: string,
|
|
16
|
-
newProjectName: string
|
|
16
|
+
newProjectName: string,
|
|
17
17
|
): void {
|
|
18
18
|
const filePathDir = dirname(filePath);
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ function updateProjectNameInConfig(
|
|
|
23
23
|
|
|
24
24
|
const targetPath = getTargetPath(
|
|
25
25
|
filePathDir,
|
|
26
|
-
prismaPhpConfigJson.phpEnvironment
|
|
26
|
+
prismaPhpConfigJson.phpEnvironment,
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
prismaPhpConfigJson.bsTarget = `http://localhost${targetPath}`;
|
|
@@ -39,59 +39,84 @@ function updateProjectNameInConfig(
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
console.log(
|
|
42
|
-
"The project name, PHP path, and other paths have been updated successfully."
|
|
42
|
+
"The project name, PHP path, and other paths have been updated successfully.",
|
|
43
43
|
);
|
|
44
|
-
}
|
|
44
|
+
},
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function getTargetPath(fullPath: string, environment: string): string {
|
|
49
49
|
const normalizedPath = normalize(fullPath);
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// ---- CI / Railway / Docker safe-guards ----
|
|
52
|
+
// GitHub Actions etc.
|
|
53
|
+
if (process.env.CI === "true") return "/";
|
|
54
|
+
|
|
55
|
+
// Railway commonly exposes these (not guaranteed, but helpful)
|
|
56
|
+
if (process.env.RAILWAY_ENVIRONMENT || process.env.RAILWAY_PROJECT_ID)
|
|
57
|
+
return "/";
|
|
58
|
+
|
|
59
|
+
// Docker/containers: your app root is usually /app
|
|
60
|
+
// If you're not inside an AMP stack folder, don't crash.
|
|
61
|
+
const lower = normalizedPath.toLowerCase();
|
|
62
|
+
if (
|
|
63
|
+
lower === "/app" ||
|
|
64
|
+
lower.startsWith("/app" + sep) ||
|
|
65
|
+
lower.startsWith("/app/")
|
|
66
|
+
) {
|
|
52
67
|
return "/";
|
|
53
68
|
}
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
// ---- Local AMP detection map (your original logic) ----
|
|
71
|
+
const webDirectories: Record<string, string> = {
|
|
56
72
|
XAMPP: join("htdocs"),
|
|
57
73
|
WAMP: join("www"),
|
|
58
74
|
MAMP: join("htdocs"),
|
|
59
75
|
LAMP: join("var", "www", "html"),
|
|
60
76
|
LEMP: join("usr", "share", "nginx", "html"),
|
|
61
77
|
AMPPS: join("www"),
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
UNIFORMSERVER: join("www"),
|
|
79
|
+
EASYPHP: join("data", "localweb"),
|
|
64
80
|
};
|
|
65
81
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
throw new Error(`Unsupported environment: ${environment}`);
|
|
69
|
-
}
|
|
82
|
+
const envKey = (environment || "").toUpperCase();
|
|
83
|
+
const webDir = webDirectories[envKey];
|
|
70
84
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.indexOf(normalize(webDir).toLowerCase());
|
|
74
|
-
if (indexOfWebDir === -1) {
|
|
75
|
-
throw new Error(`Web directory not found in path: ${webDir}`);
|
|
76
|
-
}
|
|
85
|
+
// If phpEnvironment is missing/unknown, don't crash in non-local contexts.
|
|
86
|
+
if (!webDir) return "/";
|
|
77
87
|
|
|
78
|
-
const
|
|
88
|
+
const webDirNorm = normalize(webDir).toLowerCase();
|
|
89
|
+
const idx = lower.indexOf(webDirNorm);
|
|
90
|
+
|
|
91
|
+
// If we can't find htdocs/www/etc, fall back instead of throwing.
|
|
92
|
+
if (idx === -1) return "/";
|
|
93
|
+
|
|
94
|
+
const startIndex = idx + webDir.length;
|
|
79
95
|
const subPath = normalizedPath.slice(startIndex);
|
|
96
|
+
|
|
80
97
|
const safeSeparatorRegex = new RegExp(
|
|
81
98
|
sep.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"),
|
|
82
|
-
"g"
|
|
99
|
+
"g",
|
|
83
100
|
);
|
|
84
|
-
const finalPath = subPath.replace(safeSeparatorRegex, "/") + "/";
|
|
85
101
|
|
|
86
|
-
|
|
102
|
+
const finalPath = (subPath.replace(safeSeparatorRegex, "/") || "/") + "/";
|
|
103
|
+
|
|
104
|
+
// Ensure it starts with "/"
|
|
105
|
+
return finalPath.startsWith("/") ? finalPath : `/${finalPath}`;
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
const configFilePath = join(__dirname, "..", "prisma-php.json");
|
|
90
109
|
|
|
91
|
-
|
|
110
|
+
const isLocal =
|
|
111
|
+
!process.env.CI &&
|
|
112
|
+
!process.env.RAILWAY_ENVIRONMENT &&
|
|
113
|
+
!process.env.RAILWAY_PROJECT_ID;
|
|
114
|
+
if (isLocal) {
|
|
115
|
+
updateProjectNameInConfig(configFilePath, newProjectName);
|
|
116
|
+
}
|
|
92
117
|
|
|
93
118
|
export const deleteFilesIfExist = async (
|
|
94
|
-
filePaths: string[]
|
|
119
|
+
filePaths: string[],
|
|
95
120
|
): Promise<void> => {
|
|
96
121
|
for (const filePath of filePaths) {
|
|
97
122
|
try {
|
|
@@ -113,7 +138,7 @@ export const deleteFilesIfExist = async (
|
|
|
113
138
|
};
|
|
114
139
|
|
|
115
140
|
export async function deleteDirectoriesIfExist(
|
|
116
|
-
dirPaths: string[]
|
|
141
|
+
dirPaths: string[],
|
|
117
142
|
): Promise<void> {
|
|
118
143
|
for (const dirPath of dirPaths) {
|
|
119
144
|
try {
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
|
|
6
|
+
export function generateGlobalTypes(): Plugin {
|
|
7
|
+
const dtsPath = path.resolve(process.cwd(), ".pp", "global-functions.d.ts");
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
name: "generate-global-types",
|
|
11
|
+
|
|
12
|
+
buildStart() {
|
|
13
|
+
const mainPath = path.resolve(process.cwd(), "ts", "main.ts");
|
|
14
|
+
|
|
15
|
+
if (!existsSync(mainPath)) {
|
|
16
|
+
console.warn("⚠️ ts/main.ts not found, skipping type generation");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const content = readFileSync(mainPath, "utf-8");
|
|
21
|
+
const globals = parseGlobalSingletons(content, mainPath);
|
|
22
|
+
|
|
23
|
+
if (globals.length === 0) {
|
|
24
|
+
console.warn("⚠️ No createGlobalSingleton calls found");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
generateDtsWithTypeChecker(globals, dtsPath, mainPath);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface GlobalDeclaration {
|
|
34
|
+
name: string;
|
|
35
|
+
importPath: string;
|
|
36
|
+
exportName: string;
|
|
37
|
+
isNamespace: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseGlobalSingletons(
|
|
41
|
+
content: string,
|
|
42
|
+
filePath: string
|
|
43
|
+
): GlobalDeclaration[] {
|
|
44
|
+
const sf = ts.createSourceFile(
|
|
45
|
+
filePath,
|
|
46
|
+
content,
|
|
47
|
+
ts.ScriptTarget.Latest,
|
|
48
|
+
true
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const globals: GlobalDeclaration[] = [];
|
|
52
|
+
const importMap = new Map<
|
|
53
|
+
string,
|
|
54
|
+
{ path: string; originalName: string; isNamespace: boolean }
|
|
55
|
+
>();
|
|
56
|
+
|
|
57
|
+
sf.statements.forEach((stmt) => {
|
|
58
|
+
if (ts.isImportDeclaration(stmt) && stmt.importClause) {
|
|
59
|
+
const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;
|
|
60
|
+
|
|
61
|
+
if (stmt.importClause.namedBindings) {
|
|
62
|
+
if (ts.isNamedImports(stmt.importClause.namedBindings)) {
|
|
63
|
+
stmt.importClause.namedBindings.elements.forEach((element) => {
|
|
64
|
+
const localName = element.name.text;
|
|
65
|
+
const importedName = element.propertyName
|
|
66
|
+
? element.propertyName.text
|
|
67
|
+
: localName;
|
|
68
|
+
|
|
69
|
+
importMap.set(localName, {
|
|
70
|
+
path: moduleSpecifier,
|
|
71
|
+
originalName: importedName,
|
|
72
|
+
isNamespace: false,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
} else if (ts.isNamespaceImport(stmt.importClause.namedBindings)) {
|
|
76
|
+
const localName = stmt.importClause.namedBindings.name.text;
|
|
77
|
+
importMap.set(localName, {
|
|
78
|
+
path: moduleSpecifier,
|
|
79
|
+
originalName: localName,
|
|
80
|
+
isNamespace: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
} else if (stmt.importClause.name) {
|
|
84
|
+
const localName = stmt.importClause.name.text;
|
|
85
|
+
importMap.set(localName, {
|
|
86
|
+
path: moduleSpecifier,
|
|
87
|
+
originalName: "default",
|
|
88
|
+
isNamespace: false,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
function visit(node: ts.Node) {
|
|
95
|
+
if (
|
|
96
|
+
ts.isCallExpression(node) &&
|
|
97
|
+
ts.isIdentifier(node.expression) &&
|
|
98
|
+
node.expression.text === "createGlobalSingleton"
|
|
99
|
+
) {
|
|
100
|
+
if (node.arguments.length >= 2) {
|
|
101
|
+
const nameArg = node.arguments[0];
|
|
102
|
+
const valueArg = node.arguments[1];
|
|
103
|
+
|
|
104
|
+
if (ts.isStringLiteral(nameArg) && ts.isIdentifier(valueArg)) {
|
|
105
|
+
const name = nameArg.text;
|
|
106
|
+
const variable = valueArg.text;
|
|
107
|
+
const importInfo = importMap.get(variable);
|
|
108
|
+
|
|
109
|
+
if (importInfo) {
|
|
110
|
+
globals.push({
|
|
111
|
+
name,
|
|
112
|
+
importPath: importInfo.path,
|
|
113
|
+
exportName: importInfo.originalName,
|
|
114
|
+
isNamespace: importInfo.isNamespace,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ts.forEachChild(node, visit);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
visit(sf);
|
|
124
|
+
return globals;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateDtsWithTypeChecker(
|
|
128
|
+
globals: GlobalDeclaration[],
|
|
129
|
+
dtsPath: string,
|
|
130
|
+
mainPath: string
|
|
131
|
+
) {
|
|
132
|
+
const configPath = ts.findConfigFile(
|
|
133
|
+
process.cwd(),
|
|
134
|
+
ts.sys.fileExists,
|
|
135
|
+
"tsconfig.json"
|
|
136
|
+
);
|
|
137
|
+
const { config } = configPath
|
|
138
|
+
? ts.readConfigFile(configPath, ts.sys.readFile)
|
|
139
|
+
: { config: {} };
|
|
140
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
141
|
+
config,
|
|
142
|
+
ts.sys,
|
|
143
|
+
process.cwd()
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const program = ts.createProgram(
|
|
147
|
+
parsedConfig.fileNames,
|
|
148
|
+
parsedConfig.options
|
|
149
|
+
);
|
|
150
|
+
const checker = program.getTypeChecker();
|
|
151
|
+
const sourceFile = program.getSourceFile(mainPath);
|
|
152
|
+
|
|
153
|
+
if (!sourceFile) {
|
|
154
|
+
generateFallbackDts(globals, dtsPath);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const signatures = new Map<string, string>();
|
|
159
|
+
const importMap = new Map<string, ts.ImportDeclaration>();
|
|
160
|
+
|
|
161
|
+
sourceFile.statements.forEach((stmt) => {
|
|
162
|
+
if (ts.isImportDeclaration(stmt)) {
|
|
163
|
+
if (stmt.importClause?.namedBindings) {
|
|
164
|
+
if (ts.isNamedImports(stmt.importClause.namedBindings)) {
|
|
165
|
+
stmt.importClause.namedBindings.elements.forEach((element) => {
|
|
166
|
+
importMap.set(element.name.text, stmt);
|
|
167
|
+
});
|
|
168
|
+
} else if (ts.isNamespaceImport(stmt.importClause.namedBindings)) {
|
|
169
|
+
importMap.set(stmt.importClause.namedBindings.name.text, stmt);
|
|
170
|
+
}
|
|
171
|
+
} else if (stmt.importClause?.name) {
|
|
172
|
+
importMap.set(stmt.importClause.name.text, stmt);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
globals.forEach(({ name, exportName, importPath, isNamespace }) => {
|
|
178
|
+
const isExternalLibrary =
|
|
179
|
+
!importPath.startsWith(".") && !importPath.startsWith("/");
|
|
180
|
+
|
|
181
|
+
if (isExternalLibrary) {
|
|
182
|
+
if (isNamespace) {
|
|
183
|
+
signatures.set(name, `typeof import("${importPath}")`);
|
|
184
|
+
} else {
|
|
185
|
+
signatures.set(name, `typeof import("${importPath}").${exportName}`);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const importDecl =
|
|
192
|
+
importMap.get(exportName === "default" ? name : exportName) ||
|
|
193
|
+
importMap.get(isNamespace ? name : exportName);
|
|
194
|
+
let symbol: ts.Symbol | undefined;
|
|
195
|
+
|
|
196
|
+
if (importDecl && importDecl.importClause) {
|
|
197
|
+
if (importDecl.importClause.namedBindings) {
|
|
198
|
+
if (ts.isNamedImports(importDecl.importClause.namedBindings)) {
|
|
199
|
+
const importSpec =
|
|
200
|
+
importDecl.importClause.namedBindings.elements.find(
|
|
201
|
+
(el) =>
|
|
202
|
+
(el.propertyName?.text || el.name.text) === exportName ||
|
|
203
|
+
el.name.text === exportName
|
|
204
|
+
);
|
|
205
|
+
if (importSpec)
|
|
206
|
+
symbol = checker.getSymbolAtLocation(importSpec.name);
|
|
207
|
+
} else if (
|
|
208
|
+
ts.isNamespaceImport(importDecl.importClause.namedBindings)
|
|
209
|
+
) {
|
|
210
|
+
symbol = checker.getSymbolAtLocation(
|
|
211
|
+
importDecl.importClause.namedBindings.name
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
} else if (importDecl.importClause.name) {
|
|
215
|
+
symbol = checker.getSymbolAtLocation(importDecl.importClause.name);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (symbol) {
|
|
220
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
221
|
+
const targetSymbol = aliasedSymbol || symbol;
|
|
222
|
+
const type = checker.getTypeOfSymbolAtLocation(
|
|
223
|
+
targetSymbol,
|
|
224
|
+
targetSymbol.valueDeclaration!
|
|
225
|
+
);
|
|
226
|
+
const signature = checker.typeToString(
|
|
227
|
+
type,
|
|
228
|
+
undefined,
|
|
229
|
+
ts.TypeFormatFlags.NoTruncation |
|
|
230
|
+
ts.TypeFormatFlags.UseFullyQualifiedType
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (signature !== "any") {
|
|
234
|
+
signatures.set(name, signature);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn(`Failed to resolve type for ${name}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Fallback
|
|
243
|
+
signatures.set(name, "any");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const declarations = globals
|
|
247
|
+
.map(({ name, importPath }) => {
|
|
248
|
+
const sig = signatures.get(name) || "any";
|
|
249
|
+
return ` // @source: ${importPath}\n const ${name}: ${sig};`;
|
|
250
|
+
})
|
|
251
|
+
.join("\n");
|
|
252
|
+
|
|
253
|
+
const windowDeclarations = globals
|
|
254
|
+
.map(({ name }) => ` ${name}: typeof globalThis.${name};`)
|
|
255
|
+
.join("\n");
|
|
256
|
+
|
|
257
|
+
const content = `// Auto-generated by Vite plugin
|
|
258
|
+
// Do not edit manually - regenerate with: npm run dev or npm run build
|
|
259
|
+
// Source: ts/main.ts
|
|
260
|
+
|
|
261
|
+
declare global {
|
|
262
|
+
${declarations}
|
|
263
|
+
|
|
264
|
+
interface Window {
|
|
265
|
+
${windowDeclarations}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export {};
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
const dir = path.dirname(dtsPath);
|
|
273
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
274
|
+
writeFileSync(dtsPath, content, "utf-8");
|
|
275
|
+
console.log(`✅ Generated ${path.relative(process.cwd(), dtsPath)}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function generateFallbackDts(globals: GlobalDeclaration[], dtsPath: string) {
|
|
279
|
+
const declarations = globals
|
|
280
|
+
.map(
|
|
281
|
+
({ name, importPath }) =>
|
|
282
|
+
` // @source: ${importPath}\n const ${name}: any;`
|
|
283
|
+
)
|
|
284
|
+
.join("\n");
|
|
285
|
+
|
|
286
|
+
const windowDeclarations = globals
|
|
287
|
+
.map(({ name }) => ` ${name}: typeof globalThis.${name};`)
|
|
288
|
+
.join("\n");
|
|
289
|
+
|
|
290
|
+
const content = `// Auto-generated by Vite plugin
|
|
291
|
+
declare global {
|
|
292
|
+
${declarations}
|
|
293
|
+
|
|
294
|
+
interface Window {
|
|
295
|
+
${windowDeclarations}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export {};
|
|
299
|
+
`;
|
|
300
|
+
writeFileSync(dtsPath, content, "utf-8");
|
|
301
|
+
}
|