create-prisma-php-app 4.4.4-beta → 4.4.4
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 +580 -297
- 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 +156 -18
- 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 +25 -3
- package/package.json +4 -4
|
@@ -1,9 +1,13 @@
|
|
|
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";
|
|
6
|
-
import { join, dirname } from "path";
|
|
10
|
+
import { join, dirname, relative } from "path";
|
|
7
11
|
import { getFileMeta, PUBLIC_DIR, SRC_DIR } from "./utils.js";
|
|
8
12
|
import { updateAllClassLogs } from "./class-log.js";
|
|
9
13
|
import {
|
|
@@ -13,11 +17,25 @@ 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
|
-
|
|
19
23
|
const bs: BrowserSyncInstance = browserSync.create();
|
|
20
24
|
|
|
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
|
+
}
|
|
38
|
+
|
|
21
39
|
const pipeline = new DebouncedWorker(
|
|
22
40
|
async () => {
|
|
23
41
|
await generateFileListJson();
|
|
@@ -45,18 +63,18 @@ const pipeline = new DebouncedWorker(
|
|
|
45
63
|
}
|
|
46
64
|
},
|
|
47
65
|
350,
|
|
48
|
-
"bs-pipeline"
|
|
66
|
+
"bs-pipeline",
|
|
49
67
|
);
|
|
50
68
|
|
|
51
69
|
const publicPipeline = new DebouncedWorker(
|
|
52
70
|
async () => {
|
|
53
|
-
console.log("→ Public directory changed, reloading browser...");
|
|
71
|
+
console.log(chalk.cyan("→ Public directory changed, reloading browser..."));
|
|
54
72
|
if (bs.active) {
|
|
55
73
|
bs.reload();
|
|
56
74
|
}
|
|
57
75
|
},
|
|
58
76
|
350,
|
|
59
|
-
"bs-public-pipeline"
|
|
77
|
+
"bs-public-pipeline",
|
|
60
78
|
);
|
|
61
79
|
|
|
62
80
|
createSrcWatcher(join(SRC_DIR, "**", "*"), {
|
|
@@ -68,7 +86,19 @@ createSrcWatcher(join(SRC_DIR, "**", "*"), {
|
|
|
68
86
|
});
|
|
69
87
|
|
|
70
88
|
createSrcWatcher(join(PUBLIC_DIR, "**", "*"), {
|
|
71
|
-
onEvent: (_ev,
|
|
89
|
+
onEvent: (_ev, abs, _) => {
|
|
90
|
+
const relFromPublic = relative(PUBLIC_DIR, abs);
|
|
91
|
+
const normalized = relFromPublic.replace(/\\/g, "/");
|
|
92
|
+
|
|
93
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
94
|
+
const firstSegment = segments[0] || "";
|
|
95
|
+
|
|
96
|
+
if (PUBLIC_IGNORE_DIRS.includes(firstSegment)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
publicPipeline.schedule(relFromPublic);
|
|
101
|
+
},
|
|
72
102
|
awaitWriteFinish: DEFAULT_AWF,
|
|
73
103
|
logPrefix: "watch-public",
|
|
74
104
|
usePolling: true,
|
|
@@ -77,16 +107,17 @@ createSrcWatcher(join(PUBLIC_DIR, "**", "*"), {
|
|
|
77
107
|
|
|
78
108
|
const viteFlagFile = join(__dirname, "..", ".pp", ".vite-build-complete");
|
|
79
109
|
mkdirSync(dirname(viteFlagFile), { recursive: true });
|
|
80
|
-
writeFileSync(viteFlagFile, "");
|
|
81
110
|
|
|
82
111
|
if (!existsSync(viteFlagFile)) {
|
|
83
112
|
writeFileSync(viteFlagFile, "0");
|
|
113
|
+
} else {
|
|
114
|
+
writeFileSync(viteFlagFile, "");
|
|
84
115
|
}
|
|
85
116
|
|
|
86
117
|
createSrcWatcher(viteFlagFile, {
|
|
87
118
|
onEvent: (ev) => {
|
|
88
119
|
if (ev === "change" && bs.active) {
|
|
89
|
-
console.log("→ Vite build complete, reloading browser...");
|
|
120
|
+
console.log(chalk.green("→ Vite build complete, reloading browser..."));
|
|
90
121
|
bs.reload();
|
|
91
122
|
}
|
|
92
123
|
},
|
|
@@ -99,6 +130,7 @@ createSrcWatcher(viteFlagFile, {
|
|
|
99
130
|
bs.init(
|
|
100
131
|
{
|
|
101
132
|
proxy: "http://localhost:3000",
|
|
133
|
+
online: true,
|
|
102
134
|
middleware: [
|
|
103
135
|
(_req, res, next) => {
|
|
104
136
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
@@ -106,36 +138,142 @@ bs.init(
|
|
|
106
138
|
res.setHeader("Expires", "0");
|
|
107
139
|
next();
|
|
108
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
|
+
|
|
109
150
|
createProxyMiddleware({
|
|
110
151
|
target: prismaPhpConfigJson.bsTarget,
|
|
111
152
|
changeOrigin: true,
|
|
112
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
|
+
},
|
|
113
220
|
}),
|
|
114
221
|
],
|
|
115
|
-
|
|
116
222
|
notify: false,
|
|
117
223
|
open: false,
|
|
118
224
|
ghostMode: false,
|
|
119
225
|
codeSync: true,
|
|
226
|
+
logLevel: "silent",
|
|
120
227
|
},
|
|
121
228
|
(err, bsInstance) => {
|
|
122
229
|
if (err) {
|
|
123
|
-
console.error("BrowserSync failed to start:", err);
|
|
230
|
+
console.error(chalk.red("BrowserSync failed to start:"), err);
|
|
124
231
|
return;
|
|
125
232
|
}
|
|
126
233
|
|
|
234
|
+
const bsPort = bsInstance.getOption("port");
|
|
127
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
|
+
|
|
128
266
|
const out = {
|
|
129
|
-
local:
|
|
130
|
-
external:
|
|
131
|
-
ui:
|
|
132
|
-
uiExternal:
|
|
267
|
+
local: localUrl,
|
|
268
|
+
external: externalUrl,
|
|
269
|
+
ui: uiUrl,
|
|
270
|
+
uiExternal: uiExtUrl,
|
|
133
271
|
};
|
|
134
272
|
|
|
135
273
|
writeFileSync(
|
|
136
274
|
join(__dirname, "bs-config.json"),
|
|
137
|
-
JSON.stringify(out, null, 2)
|
|
275
|
+
JSON.stringify(out, null, 2),
|
|
138
276
|
);
|
|
139
|
-
console.log("
|
|
140
|
-
}
|
|
277
|
+
console.log(`\n${chalk.gray("Press Ctrl+C to stop.")}\n`);
|
|
278
|
+
},
|
|
141
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
|
+
}
|