sliftutils 0.6.6 → 0.7.1
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/builders/electronBuild.ts +101 -0
- package/builders/electronBuildRun.js +4 -0
- package/builders/extensionBuild.ts +15 -9
- package/builders/generateIndexDts.js +46 -0
- package/builders/hotReload.ts +96 -0
- package/builders/nodeJSBuild.ts +1 -1
- package/builders/setup.ts +182 -0
- package/builders/setupRun.js +4 -0
- package/builders/watch.ts +182 -0
- package/builders/watchRun.js +4 -0
- package/builders/webBuild.ts +2 -2
- package/builders/webRun.js +4 -0
- package/bundler/bundleEntry.ts +17 -9
- package/bundler/bundleEntryCaller.ts +1 -1
- package/bundler/bundleRequire.ts +21 -3
- package/electron/electronIndex.html +12 -0
- package/electron/electronMain.ts +29 -0
- package/electron/electronRenderer.tsx +33 -0
- package/extension/extBackground.ts +22 -0
- package/extension/extContentScript.ts +19 -0
- package/extension/manifest.json +29 -0
- package/index.d.ts +332 -0
- package/misc/environment.ts +28 -0
- package/nodejs/server.ts +16 -0
- package/package.json +28 -3
- package/render-utils/DropdownCustom.d.ts +23 -0
- package/render-utils/FullscreenModal.d.ts +12 -0
- package/render-utils/Input.d.ts +39 -0
- package/render-utils/InputLabel.d.ts +35 -0
- package/render-utils/InputPicker.d.ts +35 -0
- package/render-utils/LocalStorageParam.d.ts +12 -0
- package/render-utils/SyncedController.d.ts +38 -0
- package/render-utils/SyncedLoadingIndicator.d.ts +8 -0
- package/render-utils/Table.d.ts +32 -0
- package/render-utils/URLParam.d.ts +13 -0
- package/render-utils/asyncObservable.d.ts +3 -0
- package/render-utils/index.html +12 -0
- package/render-utils/mobxTyped.d.ts +1 -0
- package/render-utils/modal.d.ts +6 -0
- package/render-utils/observer.d.ts +17 -0
- package/spec.txt +5 -64
- package/storage/FileFolderAPI.tsx +1 -1
- package/storage/PendingManager.tsx +1 -1
- package/tsconfig.declarations.json +18 -0
- package/web/browser.tsx +37 -0
- package/web/index.html +32 -0
- /package/{web → render-utils}/DropdownCustom.tsx +0 -0
- /package/{web → render-utils}/FullscreenModal.tsx +0 -0
- /package/{web → render-utils}/GenericFormat.tsx +0 -0
- /package/{web → render-utils}/Input.tsx +0 -0
- /package/{web → render-utils}/InputLabel.tsx +0 -0
- /package/{web → render-utils}/InputPicker.tsx +0 -0
- /package/{web → render-utils}/LocalStorageParam.ts +0 -0
- /package/{web → render-utils}/SyncedController.ts +0 -0
- /package/{web → render-utils}/SyncedLoadingIndicator.tsx +0 -0
- /package/{web → render-utils}/Table.tsx +0 -0
- /package/{web → render-utils}/URLParam.ts +0 -0
- /package/{web → render-utils}/asyncObservable.ts +0 -0
- /package/{web → render-utils}/colors.tsx +0 -0
- /package/{web → render-utils}/mobxTyped.ts +0 -0
- /package/{web → render-utils}/modal.tsx +0 -0
- /package/{web → render-utils}/observer.tsx +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { delay } from "socket-function/src/batching";
|
|
3
|
+
import { bundleEntryCaller } from "../bundler/bundleEntryCaller";
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { getAllFiles } from "../misc/fs";
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
let time = Date.now();
|
|
11
|
+
let yargObj = yargs(process.argv)
|
|
12
|
+
.option("mainEntry", { type: "string", default: "./electron/electronMain.ts", desc: `Path to the main process entry point` })
|
|
13
|
+
.option("rendererEntry", { type: "string", default: "./electron/electronRenderer.tsx", desc: `Path to the renderer process entry point` })
|
|
14
|
+
.option("indexHtml", { type: "string", default: "./electron/electronIndex.html", desc: `Path to the index.html file` })
|
|
15
|
+
.option("assetsFolder", { type: "string", default: "./assets", desc: `Path to the assets folder` })
|
|
16
|
+
.option("outputFolder", { type: "string", default: "./build-electron", desc: `Output folder` })
|
|
17
|
+
.argv || {}
|
|
18
|
+
;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Wait for any async functions to load.
|
|
22
|
+
await delay(0);
|
|
23
|
+
|
|
24
|
+
let hasMainEntry = fs.existsSync(yargObj.mainEntry);
|
|
25
|
+
let hasRendererEntry = fs.existsSync(yargObj.rendererEntry);
|
|
26
|
+
let hasIndexHtml = fs.existsSync(yargObj.indexHtml);
|
|
27
|
+
let hasAssets = fs.existsSync(yargObj.assetsFolder);
|
|
28
|
+
|
|
29
|
+
if (!hasMainEntry) {
|
|
30
|
+
throw new Error(`Main process entry point not found at ${yargObj.mainEntry}. Please specify with the --mainEntry option.`);
|
|
31
|
+
}
|
|
32
|
+
if (!hasRendererEntry) {
|
|
33
|
+
throw new Error(`Renderer process entry point not found at ${yargObj.rendererEntry}. Please specify with the --rendererEntry option.`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await fs.promises.mkdir(yargObj.outputFolder, { recursive: true });
|
|
37
|
+
|
|
38
|
+
// Build main and renderer processes in parallel
|
|
39
|
+
await Promise.all([
|
|
40
|
+
bundleEntryCaller({
|
|
41
|
+
entryPoint: yargObj.mainEntry,
|
|
42
|
+
outputFolder: yargObj.outputFolder,
|
|
43
|
+
}),
|
|
44
|
+
bundleEntryCaller({
|
|
45
|
+
entryPoint: yargObj.rendererEntry,
|
|
46
|
+
outputFolder: yargObj.outputFolder,
|
|
47
|
+
})
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Collect all files to copy
|
|
51
|
+
let filesToCopy: string[] = [];
|
|
52
|
+
|
|
53
|
+
if (hasIndexHtml) {
|
|
54
|
+
filesToCopy.push(yargObj.indexHtml);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add assets folder files if it exists
|
|
58
|
+
if (hasAssets) {
|
|
59
|
+
for await (const file of getAllFiles(yargObj.assetsFolder)) {
|
|
60
|
+
filesToCopy.push(file);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Copy all files with timestamp checking
|
|
65
|
+
async function getTimestamp(filePath: string): Promise<number> {
|
|
66
|
+
try {
|
|
67
|
+
const stats = await fs.promises.stat(filePath);
|
|
68
|
+
return stats.mtimeMs;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let filesCopied = 0;
|
|
75
|
+
let root = path.resolve(".");
|
|
76
|
+
for (const file of filesToCopy) {
|
|
77
|
+
let sourcePath = path.resolve(file);
|
|
78
|
+
if (!fs.existsSync(sourcePath)) {
|
|
79
|
+
console.warn(`Warning: File not found: ${file}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
let relativePath = path.relative(root, sourcePath);
|
|
83
|
+
let destPath = path.join(yargObj.outputFolder, relativePath);
|
|
84
|
+
|
|
85
|
+
let sourceTimestamp = await getTimestamp(sourcePath);
|
|
86
|
+
let destTimestamp = await getTimestamp(destPath);
|
|
87
|
+
if (sourceTimestamp > destTimestamp) {
|
|
88
|
+
await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
|
|
89
|
+
await fs.promises.cp(sourcePath, destPath);
|
|
90
|
+
filesCopied++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (filesCopied > 0) {
|
|
94
|
+
console.log(`Copied ${filesCopied} changed files`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let duration = Date.now() - time;
|
|
98
|
+
console.log(`Electron build completed in ${formatTime(duration)}`);
|
|
99
|
+
}
|
|
100
|
+
main().catch(console.error).finally(() => process.exit());
|
|
101
|
+
|
|
@@ -13,9 +13,9 @@ async function main() {
|
|
|
13
13
|
// And copy the manifest.json
|
|
14
14
|
// AND copy everything in ./assets which has updated
|
|
15
15
|
let yargObj = yargs(process.argv)
|
|
16
|
-
.option("backgroundEntry", { type: "string", default: "./extBackground.ts", desc: `Path to the entry point file` })
|
|
17
|
-
.option("contentEntry", { type: "string", default: "./extContentScript.ts", desc: `Path to the entry point file` })
|
|
18
|
-
.option("manifestPath", { type: "string", default: "./manifest.json", desc: `Path to the manifest.json file` })
|
|
16
|
+
.option("backgroundEntry", { type: "string", default: "./extension/extBackground.ts", desc: `Path to the entry point file` })
|
|
17
|
+
.option("contentEntry", { type: "string", default: "./extension/extContentScript.ts", desc: `Path to the entry point file` })
|
|
18
|
+
.option("manifestPath", { type: "string", default: "./extension/manifest.json", desc: `Path to the manifest.json file` })
|
|
19
19
|
.option("assetsFolder", { type: "string", default: "./assets", desc: `Path to the assets folder` })
|
|
20
20
|
.option("outputFolder", { type: "string", default: "./build-extension", desc: `Output folder` })
|
|
21
21
|
.argv || {}
|
|
@@ -39,19 +39,25 @@ async function main() {
|
|
|
39
39
|
|
|
40
40
|
await fs.promises.mkdir("./build-extension", { recursive: true });
|
|
41
41
|
|
|
42
|
+
// Build background and content scripts in parallel
|
|
43
|
+
let bundlePromises: Promise<void>[] = [];
|
|
42
44
|
if (hasBackgroundEntry) {
|
|
43
|
-
|
|
45
|
+
bundlePromises.push(bundleEntryCaller({
|
|
44
46
|
entryPoint: yargObj.backgroundEntry,
|
|
45
47
|
outputFolder: yargObj.outputFolder,
|
|
46
|
-
});
|
|
48
|
+
}));
|
|
47
49
|
}
|
|
48
50
|
if (hasContentEntry) {
|
|
49
|
-
|
|
51
|
+
bundlePromises.push(bundleEntryCaller({
|
|
50
52
|
entryPoint: yargObj.contentEntry,
|
|
51
53
|
outputFolder: yargObj.outputFolder,
|
|
52
|
-
});
|
|
54
|
+
}));
|
|
53
55
|
}
|
|
54
|
-
|
|
56
|
+
|
|
57
|
+
await Promise.all([
|
|
58
|
+
...bundlePromises,
|
|
59
|
+
fs.promises.cp(yargObj.manifestPath, path.join(yargObj.outputFolder, "manifest.json"))
|
|
60
|
+
]);
|
|
55
61
|
|
|
56
62
|
// Parse manifest and collect referenced files
|
|
57
63
|
let manifestContent = await fs.promises.readFile(yargObj.manifestPath, "utf-8");
|
|
@@ -133,6 +139,6 @@ async function main() {
|
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
let duration = Date.now() - time;
|
|
136
|
-
console.log(`
|
|
142
|
+
console.log(`Extension build completed in ${formatTime(duration)}`);
|
|
137
143
|
}
|
|
138
144
|
main().catch(console.error).finally(() => process.exit());
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function getAllDtsFiles(dir, fileList = []) {
|
|
5
|
+
const files = fs.readdirSync(dir);
|
|
6
|
+
|
|
7
|
+
for (const file of files) {
|
|
8
|
+
const filePath = path.join(dir, file);
|
|
9
|
+
const stat = fs.statSync(filePath);
|
|
10
|
+
|
|
11
|
+
if (stat.isDirectory()) {
|
|
12
|
+
getAllDtsFiles(filePath, fileList);
|
|
13
|
+
} else if (file.endsWith(".d.ts")) {
|
|
14
|
+
fileList.push(filePath);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return fileList;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function generateIndexDts() {
|
|
22
|
+
const renderUtilsPath = path.join(__dirname, "..", "render-utils");
|
|
23
|
+
const dtsFiles = getAllDtsFiles(renderUtilsPath);
|
|
24
|
+
|
|
25
|
+
const modules = dtsFiles
|
|
26
|
+
.map(filePath => {
|
|
27
|
+
const relativePath = path.relative(renderUtilsPath, filePath);
|
|
28
|
+
const withoutExt = relativePath.replace(/\.d\.ts$/, "");
|
|
29
|
+
const modulePath = "sliftutils/render-utils/" + withoutExt.replace(/\\/g, "/");
|
|
30
|
+
|
|
31
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
32
|
+
|
|
33
|
+
return `declare module "${modulePath}" {\n${content}\n}`;
|
|
34
|
+
})
|
|
35
|
+
.sort()
|
|
36
|
+
.join("\n\n");
|
|
37
|
+
|
|
38
|
+
const outputPath = path.join(__dirname, "..", "index.d.ts");
|
|
39
|
+
const header = `// Auto-generated file. Do not edit manually.\n// Generated by: yarn generate-index-dts\n\n`;
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(outputPath, header + modules + "\n", "utf8");
|
|
42
|
+
console.log(`Generated ${outputPath} with ${dtsFiles.length} module declarations`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
generateIndexDts();
|
|
46
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isInBrowser, isInChromeExtension, isInChromeExtensionBackground, isInChromeExtensionContentScript } from "../misc/environment";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_WATCH_PORT = 9876;
|
|
4
|
+
|
|
5
|
+
export async function enableHotReloading(config?: {
|
|
6
|
+
port?: number;
|
|
7
|
+
}) {
|
|
8
|
+
if (isInChromeExtensionBackground()) {
|
|
9
|
+
chromeExtensionBackgroundHotReload(config?.port);
|
|
10
|
+
} else if (isInChromeExtensionContentScript()) {
|
|
11
|
+
chromeExtensionContentScriptHotReload();
|
|
12
|
+
} else if (typeof window !== "undefined" && typeof window.location && typeof window.location.reload === "function") {
|
|
13
|
+
// For most reloadable environments, just refresh
|
|
14
|
+
watchPortHotReload(config?.port, () => {
|
|
15
|
+
window.location.reload();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void) {
|
|
21
|
+
let reconnectTimer: number | undefined;
|
|
22
|
+
let ws: WebSocket | undefined;
|
|
23
|
+
|
|
24
|
+
let everConnected = false;
|
|
25
|
+
|
|
26
|
+
function connect() {
|
|
27
|
+
try {
|
|
28
|
+
ws = new WebSocket(`ws://localhost:${port}`);
|
|
29
|
+
|
|
30
|
+
ws.onopen = () => {
|
|
31
|
+
console.log(`[Hot Reload] Connected to watch server on port ${port}`);
|
|
32
|
+
everConnected = true;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
ws.onmessage = (event) => {
|
|
36
|
+
try {
|
|
37
|
+
let data = JSON.parse(event.data);
|
|
38
|
+
if (data.type === "build-complete" && data.success) {
|
|
39
|
+
console.log("[Hot Reload] Build complete, reloading page...");
|
|
40
|
+
onReload();
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("[Hot Reload] Failed to parse message:", error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ws.onerror = (error) => {
|
|
48
|
+
console.warn(`[Hot Reload] WebSocket error. Use the watch script to enable watching.`);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
ws.onclose = () => {
|
|
52
|
+
if (everConnected) {
|
|
53
|
+
console.log("[Hot Reload] Disconnected from watch server, reconnecting in 2s...");
|
|
54
|
+
if (reconnectTimer) {
|
|
55
|
+
clearTimeout(reconnectTimer);
|
|
56
|
+
}
|
|
57
|
+
reconnectTimer = setTimeout(() => {
|
|
58
|
+
connect();
|
|
59
|
+
}, 2000) as any;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("[Hot Reload] Failed to connect:", error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
connect();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function chromeExtensionBackgroundHotReload(port = DEFAULT_WATCH_PORT) {
|
|
71
|
+
chrome.runtime.onConnect.addListener((port) => {
|
|
72
|
+
if (port.name === "hotReload") {
|
|
73
|
+
// Keep the port open so content scripts can detect when we disconnect
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
watchPortHotReload(port, () => {
|
|
78
|
+
chrome.runtime.reload();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function chromeExtensionContentScriptHotReload() {
|
|
83
|
+
let port = chrome.runtime.connect({ name: "hotReload" });
|
|
84
|
+
|
|
85
|
+
let startTime = Date.now();
|
|
86
|
+
|
|
87
|
+
port.onDisconnect.addListener(() => {
|
|
88
|
+
let timeToFail = Date.now() - startTime;
|
|
89
|
+
if (timeToFail > 10000) {
|
|
90
|
+
console.warn("[Hot Reload] Could not connect to background script. Make sure the background script calls enableHotReloading().");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.log("[Hot Reload] Extension reloaded, refreshing page...");
|
|
94
|
+
window.location.reload();
|
|
95
|
+
});
|
|
96
|
+
}
|
package/builders/nodeJSBuild.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { formatTime } from "socket-function/src/formatting/format";
|
|
|
7
7
|
async function main() {
|
|
8
8
|
let time = Date.now();
|
|
9
9
|
let yargObj = yargs(process.argv)
|
|
10
|
-
.option("entryPoint", { type: "string", default: "./server.ts", desc: `Path to the entry point file` })
|
|
10
|
+
.option("entryPoint", { type: "string", default: "./nodejs/server.ts", desc: `Path to the entry point file` })
|
|
11
11
|
.option("outputFolder", { type: "string", default: "./build-nodejs", desc: `Output folder` })
|
|
12
12
|
.argv || {}
|
|
13
13
|
;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
let targetDir = process.cwd();
|
|
6
|
+
let sourceDir = path.join(__dirname, "..");
|
|
7
|
+
|
|
8
|
+
console.log("Setting up sliftutils project...");
|
|
9
|
+
console.log(`Source: ${sourceDir}`);
|
|
10
|
+
console.log(`Target: ${targetDir}`);
|
|
11
|
+
|
|
12
|
+
// Directories and files to copy
|
|
13
|
+
let directoriesToScan = ["electron", "extension", "web", "nodejs", "assets", ".vscode"];
|
|
14
|
+
let rootFiles = [".cursorrules", ".eslintrc.js", ".gitignore", "tsconfig.json"];
|
|
15
|
+
|
|
16
|
+
// Import path mappings to convert relative imports to package imports
|
|
17
|
+
let importMappings: { [key: string]: string } = {
|
|
18
|
+
"../builders/": "sliftutils/builders/",
|
|
19
|
+
"../misc/": "sliftutils/misc/",
|
|
20
|
+
"../render-utils/": "sliftutils/render-utils/",
|
|
21
|
+
"../bundler/": "sliftutils/bundler/",
|
|
22
|
+
"../storage/": "sliftutils/storage/",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Gather all files to copy
|
|
26
|
+
let filesToCopy: string[] = [];
|
|
27
|
+
|
|
28
|
+
// Add root files
|
|
29
|
+
for (let file of rootFiles) {
|
|
30
|
+
let sourcePath = path.join(sourceDir, file);
|
|
31
|
+
if (fs.existsSync(sourcePath)) {
|
|
32
|
+
filesToCopy.push(file);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Gather files from directories
|
|
37
|
+
for (let dir of directoriesToScan) {
|
|
38
|
+
let sourcePath = path.join(sourceDir, dir);
|
|
39
|
+
if (fs.existsSync(sourcePath)) {
|
|
40
|
+
let filesInDir = gatherFilesRecursive(sourcePath, sourceDir);
|
|
41
|
+
filesToCopy.push(...filesInDir);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Copy all files
|
|
46
|
+
console.log(`\nFound ${filesToCopy.length} files to copy\n`);
|
|
47
|
+
|
|
48
|
+
for (let relativePath of filesToCopy) {
|
|
49
|
+
let sourcePath = path.join(sourceDir, relativePath);
|
|
50
|
+
let targetPath = path.join(targetDir, relativePath);
|
|
51
|
+
|
|
52
|
+
// Check if target already exists
|
|
53
|
+
if (fs.existsSync(targetPath)) {
|
|
54
|
+
console.log(`Skipping ${relativePath} (already exists)`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Create directory if needed
|
|
59
|
+
let targetDirPath = path.dirname(targetPath);
|
|
60
|
+
if (!fs.existsSync(targetDirPath)) {
|
|
61
|
+
fs.mkdirSync(targetDirPath, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Copy file with import processing for .ts/.tsx files
|
|
65
|
+
if (relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) {
|
|
66
|
+
let content = fs.readFileSync(sourcePath, "utf8");
|
|
67
|
+
let processedContent = replaceImports(content, importMappings);
|
|
68
|
+
fs.writeFileSync(targetPath, processedContent, "utf8");
|
|
69
|
+
console.log(`Copied ${relativePath} (with import mapping)`);
|
|
70
|
+
} else {
|
|
71
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
72
|
+
console.log(`Copied ${relativePath}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update package.json with scripts
|
|
77
|
+
let packageJsonPath = path.join(targetDir, "package.json");
|
|
78
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
79
|
+
console.log("\nUpdating package.json scripts...");
|
|
80
|
+
updatePackageJson(packageJsonPath);
|
|
81
|
+
} else {
|
|
82
|
+
console.warn("\nNo package.json found in target directory");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log("\nSetup complete!");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function gatherFilesRecursive(dir: string, baseDir: string): string[] {
|
|
89
|
+
let files: string[] = [];
|
|
90
|
+
let entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
91
|
+
|
|
92
|
+
for (let entry of entries) {
|
|
93
|
+
let fullPath = path.join(dir, entry.name);
|
|
94
|
+
|
|
95
|
+
if (entry.isDirectory()) {
|
|
96
|
+
// Skip dist directories
|
|
97
|
+
if (entry.name === "dist") {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let subFiles = gatherFilesRecursive(fullPath, baseDir);
|
|
101
|
+
files.push(...subFiles);
|
|
102
|
+
} else {
|
|
103
|
+
// Add file as relative path from base directory
|
|
104
|
+
let relativePath = path.relative(baseDir, fullPath);
|
|
105
|
+
files.push(relativePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return files;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function replaceImports(content: string, importMappings: { [key: string]: string }): string {
|
|
113
|
+
let lines = content.split("\n");
|
|
114
|
+
let processedLines = lines.map(line => {
|
|
115
|
+
// Check if line contains an import or require statement
|
|
116
|
+
if (line.includes("import") || line.includes("require")) {
|
|
117
|
+
let processedLine = line;
|
|
118
|
+
for (let [oldPath, newPath] of Object.entries(importMappings)) {
|
|
119
|
+
processedLine = processedLine.replace(new RegExp(oldPath.replace(/\//g, "\\/"), "g"), newPath);
|
|
120
|
+
}
|
|
121
|
+
return processedLine;
|
|
122
|
+
}
|
|
123
|
+
return line;
|
|
124
|
+
});
|
|
125
|
+
return processedLines.join("\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function updatePackageJson(packageJsonPath: string) {
|
|
129
|
+
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
130
|
+
|
|
131
|
+
// Read our current package.json to get the type script
|
|
132
|
+
let sourcePackageJsonPath = path.join(__dirname, "..", "package.json");
|
|
133
|
+
let sourcePackageJson = JSON.parse(fs.readFileSync(sourcePackageJsonPath, "utf8"));
|
|
134
|
+
|
|
135
|
+
if (!packageJson.scripts) {
|
|
136
|
+
packageJson.scripts = {};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Add type script (copied from source)
|
|
140
|
+
if (!packageJson.scripts.type) {
|
|
141
|
+
packageJson.scripts.type = sourcePackageJson.scripts.type;
|
|
142
|
+
console.log(" Added 'type' script");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Copy run commands from source (except run-web)
|
|
146
|
+
let copiedCommands = ["run-nodejs", "run-nodejs-dev", "run-electron"];
|
|
147
|
+
for (let cmd of copiedCommands) {
|
|
148
|
+
if (!packageJson.scripts[cmd]) {
|
|
149
|
+
packageJson.scripts[cmd] = sourcePackageJson.scripts[cmd];
|
|
150
|
+
console.log(` Added '${cmd}' script`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add hard-coded commands
|
|
155
|
+
let hardCodedCommands: { [key: string]: string } = {
|
|
156
|
+
"run-web": "node ./node_modules/sliftutils/builders/webRun.js",
|
|
157
|
+
"build-nodejs": "build-nodejs",
|
|
158
|
+
"build-web": "build-web",
|
|
159
|
+
"build-extension": "build-extension",
|
|
160
|
+
"build-electron": "build-electron",
|
|
161
|
+
"watch-nodejs": "slift-watch --port 9876 \"nodejs/*.ts\" \"nodejs/*.tsx\" \"yarn build-nodejs\"",
|
|
162
|
+
"watch-web": "slift-watch --port 9877 \"web/*.ts\" \"web/*.tsx\" \"yarn build-web\"",
|
|
163
|
+
"watch-extension": "slift-watch --port 9878 \"extension/*.ts\" \"extension/*.tsx\" \"yarn build-extension\"",
|
|
164
|
+
"watch-electron": "slift-watch --port 9879 \"electron/*.ts\" \"electron/*.tsx\" \"yarn build-electron\"",
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
for (let [scriptName, command] of Object.entries(hardCodedCommands)) {
|
|
168
|
+
if (!packageJson.scripts[scriptName]) {
|
|
169
|
+
packageJson.scripts[scriptName] = command;
|
|
170
|
+
console.log(` Added '${scriptName}' script`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 4) + "\n", "utf8");
|
|
175
|
+
console.log(" package.json updated");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main().catch(error => {
|
|
179
|
+
console.error("Setup failed:", error);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
|
182
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { runPromise } from "socket-function/src/runPromise";
|
|
4
|
+
import { WebSocketServer } from "ws";
|
|
5
|
+
|
|
6
|
+
const DEBOUNCE_DELAY_MS = 250;
|
|
7
|
+
const DEFAULT_WATCH_PORT = 9876;
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
let args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
if (args.length < 2) {
|
|
13
|
+
console.error("Usage: watch [--port PORT] <pattern1> [pattern2...] <command>");
|
|
14
|
+
console.error("Example: watch --port 9877 '*.ts' '*.tsx' 'yarn build'");
|
|
15
|
+
console.error(" - Patterns with wildcards must match the entire path");
|
|
16
|
+
console.error(" - Patterns without wildcards can appear anywhere in the path");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Parse port parameter if present
|
|
21
|
+
let watchPort = DEFAULT_WATCH_PORT;
|
|
22
|
+
let argIndex = 0;
|
|
23
|
+
|
|
24
|
+
if (args[argIndex] === "--port") {
|
|
25
|
+
if (args.length < 4) {
|
|
26
|
+
console.error("--port requires a value");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
watchPort = parseInt(args[argIndex + 1], 10);
|
|
30
|
+
if (isNaN(watchPort)) {
|
|
31
|
+
console.error(`Invalid port number: ${args[argIndex + 1]}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
argIndex += 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let remainingArgs = args.slice(argIndex);
|
|
38
|
+
let patterns = remainingArgs.slice(0, -1);
|
|
39
|
+
let command = remainingArgs[remainingArgs.length - 1];
|
|
40
|
+
|
|
41
|
+
let currentDirectory = process.cwd();
|
|
42
|
+
|
|
43
|
+
// Setup WebSocket server (unless disabled with port <= 0)
|
|
44
|
+
let wss: WebSocketServer | undefined;
|
|
45
|
+
if (watchPort > 0) {
|
|
46
|
+
wss = new WebSocketServer({ port: watchPort });
|
|
47
|
+
console.log(`WebSocket server listening on port ${watchPort}. Use --port parameter to change this. Set it to 0 to disable watching.`);
|
|
48
|
+
|
|
49
|
+
wss.on("connection", (ws) => {
|
|
50
|
+
console.log(`[${new Date().toLocaleTimeString()}] WebSocket client connected`);
|
|
51
|
+
ws.on("close", () => {
|
|
52
|
+
console.log(`[${new Date().toLocaleTimeString()}] WebSocket client disconnected`);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`WebSocket server disabled (port <= 0)`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`Watching patterns: ${patterns.join(", ")}`);
|
|
60
|
+
console.log(`Running command: ${command}`);
|
|
61
|
+
console.log("");
|
|
62
|
+
|
|
63
|
+
let debounceTimer: NodeJS.Timeout | undefined;
|
|
64
|
+
let isRunning = false;
|
|
65
|
+
let needsRerun = false;
|
|
66
|
+
|
|
67
|
+
async function executeCommand() {
|
|
68
|
+
if (isRunning) {
|
|
69
|
+
needsRerun = true;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isRunning = true;
|
|
74
|
+
needsRerun = false;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
console.log(`\n[${new Date().toLocaleTimeString()}] Running: ${command}`);
|
|
78
|
+
await runPromise(command);
|
|
79
|
+
console.log(`[${new Date().toLocaleTimeString()}] Completed successfully`);
|
|
80
|
+
|
|
81
|
+
// Notify all connected WebSocket clients
|
|
82
|
+
if (wss) {
|
|
83
|
+
wss.clients.forEach((client) => {
|
|
84
|
+
if (client.readyState === 1) { // 1 = OPEN
|
|
85
|
+
client.send(JSON.stringify({ type: "build-complete", success: true }));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`[${new Date().toLocaleTimeString()}] Error:`, error);
|
|
91
|
+
|
|
92
|
+
// Notify clients about build error
|
|
93
|
+
if (wss) {
|
|
94
|
+
wss.clients.forEach((client) => {
|
|
95
|
+
if (client.readyState === 1) { // 1 = OPEN
|
|
96
|
+
client.send(JSON.stringify({ type: "build-complete", success: false }));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
isRunning = false;
|
|
102
|
+
|
|
103
|
+
if (needsRerun) {
|
|
104
|
+
console.log(`[${new Date().toLocaleTimeString()}] Detected changes during build, running again...`);
|
|
105
|
+
await executeCommand();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function scheduleRun() {
|
|
111
|
+
if (debounceTimer) {
|
|
112
|
+
clearTimeout(debounceTimer);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
debounceTimer = setTimeout(() => {
|
|
116
|
+
void executeCommand();
|
|
117
|
+
}, DEBOUNCE_DELAY_MS);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function matchesPattern(filePath: string, pattern: string): boolean {
|
|
121
|
+
let relativePath = path.relative(currentDirectory, filePath).replace(/\\/g, "/").toLowerCase();
|
|
122
|
+
let lowerPattern = pattern.toLowerCase();
|
|
123
|
+
|
|
124
|
+
// If pattern contains wildcards, do full pattern matching
|
|
125
|
+
if (lowerPattern.includes("*")) {
|
|
126
|
+
// Convert wildcard pattern to regex
|
|
127
|
+
let regexPattern = lowerPattern
|
|
128
|
+
.replace(/\./g, "\\.") // Escape dots
|
|
129
|
+
.replace(/\*/g, ".*"); // * matches anything
|
|
130
|
+
let regex = new RegExp(`^${regexPattern}$`);
|
|
131
|
+
return regex.test(relativePath);
|
|
132
|
+
} else {
|
|
133
|
+
// No wildcards - pattern can appear anywhere in the path
|
|
134
|
+
return relativePath.includes(lowerPattern);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function shouldWatch(filePath: string): boolean {
|
|
139
|
+
for (let pattern of patterns) {
|
|
140
|
+
if (matchesPattern(filePath, pattern)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function watchDirectory(dir: string) {
|
|
148
|
+
try {
|
|
149
|
+
let watcher = fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
150
|
+
if (!filename) return;
|
|
151
|
+
|
|
152
|
+
let fullPath = path.join(dir, filename);
|
|
153
|
+
|
|
154
|
+
if (shouldWatch(fullPath)) {
|
|
155
|
+
console.log(`[${new Date().toLocaleTimeString()}] Detected change: ${path.relative(currentDirectory, fullPath)}`);
|
|
156
|
+
scheduleRun();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
watcher.on("error", (error) => {
|
|
161
|
+
console.error(`Watch error for ${dir}:`, error);
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`Failed to watch directory ${dir}:`, error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Start watching the current directory
|
|
169
|
+
watchDirectory(currentDirectory);
|
|
170
|
+
|
|
171
|
+
// Run the command once on startup
|
|
172
|
+
console.log("Initial build starting...");
|
|
173
|
+
await executeCommand();
|
|
174
|
+
|
|
175
|
+
console.log("\nWatching for changes... (Press Ctrl+C to exit)");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main().catch(error => {
|
|
179
|
+
console.error("Fatal error:", error);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
|
182
|
+
|