Showing the changes to scripts (1 of 2 files). Show all
Unified · Split
| @@ -0,0 +1,236 @@ | ||
| 1 | +import { | |
| 2 | + copyFileSync, | |
| 3 | + existsSync, | |
| 4 | + lstatSync, | |
| 5 | + mkdirSync, | |
| 6 | + readdirSync, | |
| 7 | + readFileSync, | |
| 8 | + readlinkSync, | |
| 9 | + rmSync, | |
| 10 | + symlinkSync, | |
| 11 | +} from "node:fs"; | |
| 12 | +import { dirname, join, relative } from "node:path"; | |
| 13 | + | |
| 14 | +const root = process.cwd(); | |
| 15 | +const out = join(root, ".selfhost-runtime"); | |
| 16 | +const appName = "@executor-js/host-selfhost"; | |
| 17 | + | |
| 18 | +const packageRoots = ["apps", "packages", "examples"] as const; | |
| 19 | +const packagePruneExtensions = [".map", ".d.ts", ".d.mts", ".d.cts", ".md", ".markdown"]; | |
| 20 | +const nodeModulePruneExtensions = [...packagePruneExtensions, ".ts", ".tsx"]; | |
| 21 | +const sourcePruneExtensions = [".test.ts", ".test.tsx"]; | |
| 22 | +const prunedDirNames = new Set([ | |
| 23 | + ".git", | |
| 24 | + ".turbo", | |
| 25 | + "test", | |
| 26 | + "tests", | |
| 27 | + "__tests__", | |
| 28 | + "fixtures", | |
| 29 | + "fixture", | |
| 30 | + "examples", | |
| 31 | + "__snapshots__", | |
| 32 | +]); | |
| 33 | +const packageConfigFiles = new Set([ | |
| 34 | + "tsconfig.json", | |
| 35 | + "tsup.config.ts", | |
| 36 | + "vite.config.ts", | |
| 37 | + "vitest.config.ts", | |
| 38 | + "CHANGELOG.md", | |
| 39 | +]); | |
| 40 | + | |
| 41 | +const nodeModulesPackagePrunes = [ | |
| 42 | + /^@ant-design\+/, | |
| 43 | + /^@base-ui\+/, | |
| 44 | + /^@cloudflare\+workers-types@/, | |
| 45 | + /^@electric-sql\+pglite@/, | |
| 46 | + /^@emotion\+react@/, | |
| 47 | + /^@emoji-mart\+/, | |
| 48 | + /^@esbuild\+.*linux-x64-musl/, | |
| 49 | + /^@esbuild\+/, | |
| 50 | + /^@floating-ui\+react@/, | |
| 51 | + /^@dnd-kit\+/, | |
| 52 | + /^@hookform\+/, | |
| 53 | + /^@lobehub\+/, | |
| 54 | + /^@mermaid-js\+/, | |
| 55 | + /^@pierre\+diffs@/, | |
| 56 | + /^@pierre\+theme@/, | |
| 57 | + /^@primer\+octicons@/, | |
| 58 | + /^@rc-component\+/, | |
| 59 | + /^@rolldown\+binding-/, | |
| 60 | + /^@reduxjs\+/, | |
| 61 | + /^@shikijs\+/, | |
| 62 | + /^@splinetool\+/, | |
| 63 | + /^@tanstack\+start-plugin-core@/, | |
| 64 | + /^@tanstack\+react-router@/, | |
| 65 | + /^@tanstack\+router-core@/, | |
| 66 | + /^@types\+/, | |
| 67 | + /^ahooks@/, | |
| 68 | + /^antd@/, | |
| 69 | + /^better-sqlite3@/, | |
| 70 | + /^bun-types@/, | |
| 71 | + /^caniuse-lite@/, | |
| 72 | + /^chroma-js@/, | |
| 73 | + /^cytoscape/, | |
| 74 | + /^d3@/, | |
| 75 | + /^date-fns/, | |
| 76 | + /^drizzle-kit@/, | |
| 77 | + /^dompurify@/, | |
| 78 | + /^emoji-mart@/, | |
| 79 | + /^framer-motion@/, | |
| 80 | + /^jiti@/, | |
| 81 | + /^katex@/, | |
| 82 | + /^layout-base@/, | |
| 83 | + /^leva@/, | |
| 84 | + /^lightningcss-/, | |
| 85 | + /^lit-html@/, | |
| 86 | + /^lucide-react@/, | |
| 87 | + /^mermaid@/, | |
| 88 | + /^motion@/, | |
| 89 | + /^motion-/, | |
| 90 | + /^polished@/, | |
| 91 | + /^prettier@/, | |
| 92 | + /^react-day-picker@/, | |
| 93 | + /^react-hook-form@/, | |
| 94 | + /^react@/, | |
| 95 | + /^react-dom@/, | |
| 96 | + /^react-dropzone@/, | |
| 97 | + /^recharts@/, | |
| 98 | + /^rolldown@/, | |
| 99 | + /^shiki@/, | |
| 100 | + /^solid-js@/, | |
| 101 | + /^source-map@/, | |
| 102 | + /^tailwind-merge@/, | |
| 103 | + /^tsx@/, | |
| 104 | + /^@upsetjs\+venn\.js@/, | |
| 105 | + /^victory-vendor@/, | |
| 106 | + /^virtua@/, | |
| 107 | + /^typeorm@/, | |
| 108 | + /^vite@/, | |
| 109 | + /^vitest@/, | |
| 110 | +]; | |
| 111 | + | |
| 112 | +type PackageJson = { | |
| 113 | + readonly name?: string; | |
| 114 | + readonly dependencies?: Record<string, string>; | |
| 115 | + readonly optionalDependencies?: Record<string, string>; | |
| 116 | +}; | |
| 117 | + | |
| 118 | +type WorkspacePackage = { | |
| 119 | + readonly name: string; | |
| 120 | + readonly dir: string; | |
| 121 | + readonly packageJson: PackageJson; | |
| 122 | +}; | |
| 123 | + | |
| 124 | +const readJson = <T>(path: string): T => JSON.parse(readFileSync(path, "utf8")) as T; | |
| 125 | + | |
| 126 | +const copyRecursive = ( | |
| 127 | + source: string, | |
| 128 | + target: string, | |
| 129 | + shouldSkip: ( | |
| 130 | + source: string, | |
| 131 | + relativePath: string, | |
| 132 | + stats: ReturnType<typeof lstatSync>, | |
| 133 | + ) => boolean, | |
| 134 | +) => { | |
| 135 | + const stats = lstatSync(source); | |
| 136 | + const rel = relative(root, source); | |
| 137 | + if (shouldSkip(source, rel, stats)) return; | |
| 138 | + | |
| 139 | + if (stats.isSymbolicLink()) { | |
| 140 | + mkdirSync(dirname(target), { recursive: true }); | |
| 141 | + symlinkSync(readlinkSync(source), target); | |
| 142 | + return; | |
| 143 | + } | |
| 144 | + | |
| 145 | + if (stats.isDirectory()) { | |
| 146 | + mkdirSync(target, { recursive: true }); | |
| 147 | + for (const entry of readdirSync(source)) { | |
| 148 | + copyRecursive(join(source, entry), join(target, entry), shouldSkip); | |
| 149 | + } | |
| 150 | + return; | |
| 151 | + } | |
| 152 | + | |
| 153 | + if (stats.isFile()) { | |
| 154 | + mkdirSync(dirname(target), { recursive: true }); | |
| 155 | + copyFileSync(source, target); | |
| 156 | + } | |
| 157 | +}; | |
| 158 | + | |
| 159 | +const discoverWorkspacePackages = (): Map<string, WorkspacePackage> => { | |
| 160 | + const packages = new Map<string, WorkspacePackage>(); | |
| 161 | + for (const rootName of packageRoots) { | |
| 162 | + const rootDir = join(root, rootName); | |
| 163 | + if (!existsSync(rootDir)) continue; | |
| 164 | + for (const first of readdirSync(rootDir)) { | |
| 165 | + const firstDir = join(rootDir, first); | |
| 166 | + if (!lstatSync(firstDir).isDirectory()) continue; | |
| 167 | + const candidates = [firstDir, ...readdirSync(firstDir).map((name) => join(firstDir, name))]; | |
| 168 | + for (const candidate of candidates) { | |
| 169 | + const packageJsonPath = join(candidate, "package.json"); | |
| 170 | + if (!existsSync(packageJsonPath)) continue; | |
| 171 | + const packageJson = readJson<PackageJson>(packageJsonPath); | |
| 172 | + if (packageJson.name) { | |
| 173 | + packages.set(packageJson.name, { name: packageJson.name, dir: candidate, packageJson }); | |
| 174 | + } | |
| 175 | + } | |
| 176 | + } | |
| 177 | + } | |
| 178 | + return packages; | |
| 179 | +}; | |
| 180 | + | |
| 181 | +const workspacePackages = discoverWorkspacePackages(); | |
| 182 | +const target = workspacePackages.get(appName); | |
| 183 | +if (!target) throw new Error(`Could not find ${appName}`); | |
| 184 | + | |
| 185 | +const reachable = new Map<string, WorkspacePackage>(); | |
| 186 | +const queue = [target]; | |
| 187 | +for (const current of queue) { | |
| 188 | + if (reachable.has(current.name)) continue; | |
| 189 | + reachable.set(current.name, current); | |
| 190 | + const dependencies = { | |
| 191 | + ...current.packageJson.dependencies, | |
| 192 | + ...current.packageJson.optionalDependencies, | |
| 193 | + }; | |
| 194 | + for (const dependencyName of Object.keys(dependencies)) { | |
| 195 | + const workspaceDependency = workspacePackages.get(dependencyName); | |
| 196 | + if (workspaceDependency) queue.push(workspaceDependency); | |
| 197 | + } | |
| 198 | +} | |
| 199 | + | |
| 200 | +rmSync(out, { recursive: true, force: true }); | |
| 201 | +mkdirSync(out, { recursive: true }); | |
| 202 | + | |
| 203 | +copyRecursive(join(root, "package.json"), join(out, "package.json"), () => false); | |
| 204 | +copyRecursive(join(root, "bun.lock"), join(out, "bun.lock"), () => false); | |
| 205 | + | |
| 206 | +copyRecursive(join(root, "node_modules"), join(out, "node_modules"), (source, rel, stats) => { | |
| 207 | + const base = source.split("/node_modules/.bun/")[1]?.split("/")[0]; | |
| 208 | + if (base?.includes("linux-x64-musl")) return true; | |
| 209 | + if (base && nodeModulesPackagePrunes.some((pattern) => pattern.test(base))) return true; | |
| 210 | + const name = source.split("/").at(-1) ?? ""; | |
| 211 | + if (stats.isDirectory() && source !== join(root, "node_modules") && prunedDirNames.has(name)) | |
| 212 | + return true; | |
| 213 | + if (stats.isFile() && nodeModulePruneExtensions.some((extension) => source.endsWith(extension))) | |
| 214 | + return true; | |
| 215 | + return false; | |
| 216 | +}); | |
| 217 | + | |
| 218 | +for (const pkg of reachable.values()) { | |
| 219 | + const destination = join(out, relative(root, pkg.dir)); | |
| 220 | + copyRecursive(pkg.dir, destination, (source, rel, stats) => { | |
| 221 | + const name = source.split("/").at(-1) ?? ""; | |
| 222 | + if (stats.isDirectory() && prunedDirNames.has(name)) return true; | |
| 223 | + if (stats.isFile() && packageConfigFiles.has(name)) return true; | |
| 224 | + if (stats.isFile() && sourcePruneExtensions.some((extension) => source.endsWith(extension))) | |
| 225 | + return true; | |
| 226 | + if (stats.isFile() && packagePruneExtensions.some((extension) => source.endsWith(extension))) | |
| 227 | + return true; | |
| 228 | + if (rel === "apps/host-selfhost/Dockerfile" || rel === "apps/host-selfhost/docker-compose.yml") | |
| 229 | + return true; | |
| 230 | + if (rel.startsWith("apps/host-selfhost/web/")) return true; | |
| 231 | + if (rel.startsWith("apps/host-selfhost/scripts/")) return true; | |
| 232 | + return false; | |
| 233 | + }); | |
| 234 | +} | |
| 235 | + | |
| 236 | +console.log(`Packaged ${reachable.size} workspace packages into ${relative(root, out)}`); | |