six-degrees 0.5.0 → 0.6.0
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +14 -14
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +20 -19
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +20 -19
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +24 -23
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +22 -21
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +24 -23
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +24 -23
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +24 -23
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +24 -23
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +24 -23
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +24 -23
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +24 -23
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +24 -23
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13dx4sd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__18bcnu4._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +9 -9
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0jdkqgi._.js → _1-658fq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +13 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +13 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0gtv9jb._.js → app_components_CompanyScores_1g0mytz.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -12
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -6
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +4 -4
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{lib_0i8v-s3._.js → lib_1c4ieuw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +22 -0
- package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
- package/.next/{static/chunks/2wgz9ietkqqe6.js → standalone/.next/static/chunks/0i02yxppzds1o.js} +1 -1
- package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
- package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +1 -0
- package/.next/standalone/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
- package/.next/{static/chunks/3sowbvbaj1s6n.js → standalone/.next/static/chunks/0ws7zy0w57ufi.js} +1 -1
- package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
- package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +34 -0
- package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
- package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
- package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
- package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +45 -0
- package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +1 -0
- package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +11 -0
- package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
- package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +1 -0
- package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
- package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +1 -0
- package/.next/standalone/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
- package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +11 -0
- package/.next/standalone/CHANGELOG.md +61 -0
- package/.next/standalone/README.md +22 -8
- package/.next/standalone/app/api/scraper/route.js +40 -3
- package/.next/standalone/app/api/update/route.js +12 -6
- package/.next/standalone/app/components/AppHeader.js +257 -0
- package/.next/standalone/app/components/Avatar.js +2 -1
- package/.next/standalone/app/components/ChainView.js +12 -12
- package/.next/standalone/app/components/FilterPanel.js +20 -12
- package/.next/standalone/app/components/ForceGraph.js +163 -25
- package/.next/standalone/app/components/GalaxyLab.js +43 -12
- package/.next/standalone/app/components/GridView.js +4 -4
- package/.next/standalone/app/components/LinkedInLimits.js +2 -1
- package/.next/standalone/app/components/ListView.js +3 -3
- package/.next/standalone/app/components/NoteDetail.js +2 -1
- package/.next/standalone/app/components/OnboardingGate.js +2 -2
- package/.next/standalone/app/components/OutlinkQuest.js +2 -1
- package/.next/standalone/app/components/PathsAnalyzer.js +3 -2
- package/.next/standalone/app/components/PeopleMap.js +2 -1
- package/.next/standalone/app/components/RingsView.js +3 -3
- package/.next/standalone/app/components/ScanTrail.js +85 -0
- package/.next/standalone/app/components/SeparationView.js +100 -77
- package/.next/standalone/app/components/Sidebar.js +4 -4
- package/.next/standalone/app/components/TheirCircle.js +2 -1
- package/.next/standalone/app/components/ThemeLoader.js +9 -0
- package/.next/standalone/app/components/UpdatePanel.js +20 -2
- package/.next/standalone/app/components/settings/AppearanceSection.js +226 -0
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +2 -1
- package/.next/standalone/app/components/ui.js +2 -1
- package/.next/standalone/app/globals.css +70 -2
- package/.next/standalone/app/import/page.js +3 -3
- package/.next/standalone/app/layout.js +11 -2
- package/.next/standalone/app/page.js +69 -215
- package/.next/standalone/app/paths/page.js +8 -21
- package/.next/standalone/app/profile/page.js +11 -10
- package/.next/standalone/app/queue/page.js +8 -21
- package/.next/standalone/app/scores/page.js +1 -1
- package/.next/standalone/app/settings/page.js +7 -3
- package/.next/standalone/app/setup/page.js +83 -88
- package/.next/standalone/app/social/Crm.js +2 -1
- package/.next/standalone/app/social/SocialHub.js +2 -2
- package/.next/standalone/app/social/page.js +1 -1
- package/.next/standalone/lib/data-import.js +1 -0
- package/.next/standalone/lib/galaxy-export.js +2 -1
- package/.next/standalone/lib/galaxy-lab.js +24 -8
- package/.next/standalone/lib/level.js +51 -0
- package/.next/standalone/lib/release.js +13 -7
- package/.next/standalone/lib/scanner-python.js +38 -8
- package/.next/standalone/lib/scraper-client.js +2 -1
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/theme-store.js +61 -0
- package/.next/standalone/lib/themes.js +186 -0
- package/.next/standalone/lib/tiers.js +2 -8
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +4 -4
- package/.next/standalone/scripts/build-desktop.mjs +325 -0
- package/.next/standalone/scripts/build-site.mjs +85 -15
- package/.next/standalone/scripts/gen-test-network.mjs +93 -0
- package/.next/standalone/scripts/next.mjs +20 -0
- package/.next/standalone/scripts/readme_buttons.py +3 -0
- package/.next/standalone/scripts/requirements.txt +18 -1
- package/.next/standalone/scripts/site-news.mjs +54 -9
- package/.next/standalone/scripts/smoke-desktop.mjs +177 -0
- package/.next/standalone/scripts/windows/six-degrees.iss +100 -0
- package/.next/static/chunks/02pq76u7y4v9-.js +22 -0
- package/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
- package/.next/{standalone/.next/static/chunks/2wgz9ietkqqe6.js → static/chunks/0i02yxppzds1o.js} +1 -1
- package/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
- package/.next/static/chunks/0oyslj62jkubd.js +1 -0
- package/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
- package/.next/{standalone/.next/static/chunks/3sowbvbaj1s6n.js → static/chunks/0ws7zy0w57ufi.js} +1 -1
- package/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
- package/.next/static/chunks/172e10mi-tpo_.js +34 -0
- package/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
- package/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
- package/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
- package/.next/static/chunks/1m3017gp790pw.js +45 -0
- package/.next/static/chunks/1t20e8u0st7ep.js +1 -0
- package/.next/static/chunks/2-ch9rm27eril.js +11 -0
- package/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
- package/.next/static/chunks/3bp284gjf7dyq.js +1 -0
- package/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
- package/.next/static/chunks/3l4t8je6iguzc.js +1 -0
- package/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
- package/.next/static/chunks/42m0bqr8pbovz.js +11 -0
- package/CHANGELOG.md +61 -0
- package/README.md +22 -8
- package/lib/data-import.js +1 -0
- package/lib/galaxy-export.js +2 -1
- package/lib/galaxy-lab.js +24 -8
- package/lib/level.js +51 -0
- package/lib/release.js +13 -7
- package/lib/scanner-python.js +38 -8
- package/lib/scraper-client.js +2 -1
- package/lib/separation.js +4 -2
- package/lib/theme-store.js +61 -0
- package/lib/themes.js +186 -0
- package/lib/tiers.js +2 -8
- package/package.json +4 -4
- package/scripts/requirements.txt +18 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +0 -9
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +0 -9
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +0 -3
- package/.next/standalone/.next/static/chunks/03bziijw5srol.js +0 -1
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +0 -34
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +0 -2
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +0 -1
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +0 -1
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +0 -43
- package/.next/standalone/.next/static/chunks/2dwtoszeq91us.js +0 -1
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +0 -1
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +0 -1
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +0 -1
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +0 -1
- package/.next/static/chunks/03bziijw5srol.js +0 -1
- package/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
- package/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
- package/.next/static/chunks/0g_fy2034bp0x.js +0 -34
- package/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
- package/.next/static/chunks/0w-byiwb2zax8.js +0 -2
- package/.next/static/chunks/1g7jcovdejc9-.js +0 -1
- package/.next/static/chunks/1i51yylxdzht1.js +0 -1
- package/.next/static/chunks/1zf8swaasjf1e.js +0 -43
- package/.next/static/chunks/2dwtoszeq91us.js +0 -1
- package/.next/static/chunks/2rqtpccruavf2.js +0 -1
- package/.next/static/chunks/30x3oq_st7a5r.js +0 -1
- package/.next/static/chunks/355ryio1b7sm1.js +0 -1
- package/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
- package/.next/static/chunks/3fci6qi39-1n5.js +0 -1
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Build the desktop app for Windows or Linux (docs/brain/DESKTOP.md D3).
|
|
3
|
+
//
|
|
4
|
+
// node scripts/build-desktop.mjs --platform=win32 # on Windows: dist/Six-Degrees-<v>-win-x64-Setup.exe
|
|
5
|
+
// node scripts/build-desktop.mjs --platform=linux # on Linux: dist/six-degrees_<v>_amd64.deb and a .tar.gz
|
|
6
|
+
// --fast reuse the existing Next build (only while working on the packaging itself)
|
|
7
|
+
//
|
|
8
|
+
// The same app as the Mac's (scripts/build-app.mjs, left untouched): the same
|
|
9
|
+
// standalone server on the same bundled Node, the same Electron shell
|
|
10
|
+
// (desktop/), and the scanner's own Python with its packages installed, so
|
|
11
|
+
// nothing needs installing before a scan. What differs is only the packaging:
|
|
12
|
+
// Windows @electron/packager, then Inno Setup (preinstalled on GitHub's
|
|
13
|
+
// windows runners) for a one-click, per-user Setup.exe with Start
|
|
14
|
+
// Menu and Desktop shortcuts. No admin rights asked for.
|
|
15
|
+
// Linux @electron/packager, then a .deb (chrome-sandbox setuid root, which
|
|
16
|
+
// Electron needs on Ubuntu 24.04 and later) and a plain .tar.gz.
|
|
17
|
+
// Unsigned, like the Mac app until D4: Windows shows SmartScreen's "More info →
|
|
18
|
+
// Run anyway" the first time.
|
|
19
|
+
//
|
|
20
|
+
// Built on the platform it is for (release.yml has a job each): pip installs
|
|
21
|
+
// the Python's packages by running that Python. No new npm dependency: packager
|
|
22
|
+
// is the Mac build's, the icons come from sharp (Next's), Inno Setup and
|
|
23
|
+
// dpkg-deb are the runners' own.
|
|
24
|
+
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
26
|
+
import { createHash } from 'node:crypto';
|
|
27
|
+
import {
|
|
28
|
+
mkdirSync, rmSync, cpSync, writeFileSync, existsSync, readFileSync, readdirSync, renameSync, chmodSync, symlinkSync,
|
|
29
|
+
realpathSync, statSync,
|
|
30
|
+
} from 'node:fs';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import os from 'node:os';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
import { standaloneBuild, downloadVerified, IMPORTS } from '../lib/scanner-python.js';
|
|
35
|
+
|
|
36
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
37
|
+
const PLATFORM = (process.argv.find((a) => a.startsWith('--platform='))?.split('=')[1]) || process.platform;
|
|
38
|
+
if (!['win32', 'linux'].includes(PLATFORM)) {
|
|
39
|
+
console.error('Usage: node scripts/build-desktop.mjs --platform=win32|linux (the Mac app is scripts/build-app.mjs)');
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
if (PLATFORM !== process.platform) {
|
|
43
|
+
console.error(`Build the ${PLATFORM} app on ${PLATFORM}: pip installs its Python's packages by running that Python.`);
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
const WIN = PLATFORM === 'win32';
|
|
47
|
+
const ARCH = 'x64';
|
|
48
|
+
const pkg = JSON.parse(readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
49
|
+
const VERSION = pkg.version;
|
|
50
|
+
const APP_NAME = 'Six Degrees';
|
|
51
|
+
const OUT = path.join(ROOT, 'dist');
|
|
52
|
+
const STAGE = path.join(OUT, 'stage');
|
|
53
|
+
const CACHE = path.join(os.homedir(), '.cache', 'six-degrees-build');
|
|
54
|
+
// Windows' own tar (bsdtar): it reads .zip as well, and a bash step's PATH finds
|
|
55
|
+
// Git's GNU tar first, which takes "C:" for a remote host.
|
|
56
|
+
const TAR = WIN ? path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe') : 'tar';
|
|
57
|
+
|
|
58
|
+
// The same Node as the Mac app's server, pinned by its SHA-256 from nodejs.org's SHASUMS256.txt.
|
|
59
|
+
const NODE_VERSION = 'v24.21.0';
|
|
60
|
+
const NODE = WIN
|
|
61
|
+
? { file: `node-${NODE_VERSION}-win-x64.zip`, sha256: '158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541', size: 37618919, member: `node-${NODE_VERSION}-win-x64/node.exe` }
|
|
62
|
+
: { file: `node-${NODE_VERSION}-linux-x64.tar.gz`, sha256: '6e1db87ef58b8819e5d5402eff1536491b18edd8eb7bee5ef7897876e88dc5ff', size: 58088022, member: `node-${NODE_VERSION}-linux-x64/bin/node` };
|
|
63
|
+
|
|
64
|
+
function step(msg) { console.log(`\n▸ ${msg}`); }
|
|
65
|
+
function run(cmd, args, opts = {}) {
|
|
66
|
+
execFileSync(cmd, args, { stdio: 'inherit', ...opts });
|
|
67
|
+
}
|
|
68
|
+
function sha256File(file) {
|
|
69
|
+
return createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
70
|
+
}
|
|
71
|
+
async function fetchPinned({ url, file, sha256, size }, what) {
|
|
72
|
+
const archive = path.join(CACHE, file);
|
|
73
|
+
mkdirSync(CACHE, { recursive: true });
|
|
74
|
+
if (existsSync(archive) && sha256File(archive) === sha256) {
|
|
75
|
+
console.log(` ${what}: using the cached download (its SHA-256 matches the pinned one)`);
|
|
76
|
+
} else {
|
|
77
|
+
rmSync(archive, { force: true });
|
|
78
|
+
await downloadVerified(url, archive, { sha256, size, idleMs: 60000, from: new URL(url).host });
|
|
79
|
+
console.log(` ${what}: downloaded, and its SHA-256 matches the pinned one`);
|
|
80
|
+
}
|
|
81
|
+
return archive;
|
|
82
|
+
}
|
|
83
|
+
function findDirs(dir, match) {
|
|
84
|
+
const found = [];
|
|
85
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
86
|
+
if (!entry.isDirectory()) continue;
|
|
87
|
+
const at = path.join(dir, entry.name);
|
|
88
|
+
if (match(entry.name)) found.push(at);
|
|
89
|
+
else found.push(...findDirs(at, match));
|
|
90
|
+
}
|
|
91
|
+
return found;
|
|
92
|
+
}
|
|
93
|
+
function* walk(dir) {
|
|
94
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
95
|
+
const at = path.join(dir, entry.name);
|
|
96
|
+
if (entry.isDirectory()) yield* walk(at);
|
|
97
|
+
else yield at;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---- 1. the app itself (always rebuilt: build-app.mjs says why) -------------
|
|
102
|
+
rmSync(OUT, { recursive: true, force: true });
|
|
103
|
+
step('Building the app');
|
|
104
|
+
if (process.argv.includes('--fast') && existsSync(path.join(ROOT, '.next', 'standalone', 'server.js'))) {
|
|
105
|
+
console.log(' --fast: reusing the existing build (make sure it is current)');
|
|
106
|
+
} else {
|
|
107
|
+
// node, not npm: execFile can't start npm.cmd on Windows.
|
|
108
|
+
run(process.execPath, [path.join(ROOT, 'scripts', 'next.mjs'), 'build'], { cwd: ROOT });
|
|
109
|
+
run(process.execPath, [path.join(ROOT, 'scripts', 'prepare-standalone.mjs')], { cwd: ROOT });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
step('Assembling the server');
|
|
113
|
+
const SERVER_DIR = path.join(STAGE, 'server');
|
|
114
|
+
mkdirSync(STAGE, { recursive: true });
|
|
115
|
+
cpSync(path.join(ROOT, '.next', 'standalone'), SERVER_DIR, { recursive: true, verbatimSymlinks: true });
|
|
116
|
+
if (existsSync(path.join(SERVER_DIR, '.git'))) rmSync(path.join(SERVER_DIR, '.git'), { recursive: true, force: true });
|
|
117
|
+
// The files the app runs from scripts/, by name (TRAPS §27). Not apply-update.sh:
|
|
118
|
+
// only the Mac app replaces itself; here an update is the new installer, and a
|
|
119
|
+
// missing helper is one more thing that keeps the Mac swap from ever running.
|
|
120
|
+
for (const rel of ['scripts/scrape.py', 'scripts/image_store.py', 'scripts/requirements.txt', 'scripts/audit-avatars.mjs']) {
|
|
121
|
+
mkdirSync(path.join(SERVER_DIR, path.dirname(rel)), { recursive: true });
|
|
122
|
+
cpSync(path.join(ROOT, rel), path.join(SERVER_DIR, rel));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- 2. the runtime ----------------------------------------------------------
|
|
126
|
+
step(`Fetching Node ${NODE_VERSION}`);
|
|
127
|
+
const nodeArchive = await fetchPinned({ url: `https://nodejs.org/dist/${NODE_VERSION}/${NODE.file}`, ...NODE }, 'Node');
|
|
128
|
+
run(TAR, ['-xf', nodeArchive, '-C', CACHE, NODE.member]);
|
|
129
|
+
const NODE_OUT = path.join(STAGE, WIN ? 'node.exe' : 'node');
|
|
130
|
+
cpSync(path.join(CACHE, NODE.member), NODE_OUT);
|
|
131
|
+
if (!WIN) chmodSync(NODE_OUT, 0o755);
|
|
132
|
+
|
|
133
|
+
// ---- 3. the icon ---------------------------------------------------------------
|
|
134
|
+
// From desktop/icon/icon.svg, like the Mac's .icns (not app/favicon.ico: that is
|
|
135
|
+
// Next's default, Vercel's mark). A .ico is PNGs in a small directory; sharp makes the PNGs.
|
|
136
|
+
step('Making the icon');
|
|
137
|
+
const { default: sharp } = await import('sharp');
|
|
138
|
+
const svg = path.join(ROOT, 'desktop', 'icon', 'icon.svg');
|
|
139
|
+
const png = async (size) => sharp(svg, { density: 288 }).resize(size, size).png().toBuffer();
|
|
140
|
+
let ICON;
|
|
141
|
+
if (WIN) {
|
|
142
|
+
const sizes = [16, 24, 32, 48, 64, 128, 256];
|
|
143
|
+
const images = await Promise.all(sizes.map(png));
|
|
144
|
+
const header = Buffer.alloc(6 + 16 * sizes.length);
|
|
145
|
+
header.writeUInt16LE(0, 0); header.writeUInt16LE(1, 2); header.writeUInt16LE(sizes.length, 4);
|
|
146
|
+
let offset = header.length;
|
|
147
|
+
sizes.forEach((size, i) => {
|
|
148
|
+
const at = 6 + 16 * i;
|
|
149
|
+
header.writeUInt8(size >= 256 ? 0 : size, at); header.writeUInt8(size >= 256 ? 0 : size, at + 1);
|
|
150
|
+
header.writeUInt8(0, at + 2); header.writeUInt8(0, at + 3);
|
|
151
|
+
header.writeUInt16LE(1, at + 4); header.writeUInt16LE(32, at + 6);
|
|
152
|
+
header.writeUInt32LE(images[i].length, at + 8); header.writeUInt32LE(offset, at + 12);
|
|
153
|
+
offset += images[i].length;
|
|
154
|
+
});
|
|
155
|
+
ICON = path.join(STAGE, 'six-degrees.ico');
|
|
156
|
+
writeFileSync(ICON, Buffer.concat([header, ...images]));
|
|
157
|
+
} else {
|
|
158
|
+
ICON = path.join(STAGE, 'six-degrees.png');
|
|
159
|
+
writeFileSync(ICON, await png(512));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---- 4. the Electron shell ---------------------------------------------------
|
|
163
|
+
step('Building the Electron app');
|
|
164
|
+
const { packager } = await import('@electron/packager');
|
|
165
|
+
const electronVersion = JSON.parse(readFileSync(path.join(ROOT, 'node_modules', 'electron', 'package.json'), 'utf8')).version;
|
|
166
|
+
const shellSrc = path.join(STAGE, 'shell');
|
|
167
|
+
cpSync(path.join(ROOT, 'desktop'), shellSrc, { recursive: true, filter: (p) => !p.split(path.sep).includes('icon') });
|
|
168
|
+
const shellPkg = JSON.parse(readFileSync(path.join(shellSrc, 'package.json'), 'utf8'));
|
|
169
|
+
writeFileSync(path.join(shellSrc, 'package.json'), `${JSON.stringify({ ...shellPkg, version: VERSION }, null, 2)}\n`);
|
|
170
|
+
const [built] = await packager({
|
|
171
|
+
dir: shellSrc,
|
|
172
|
+
out: path.join(STAGE, 'out'),
|
|
173
|
+
overwrite: true,
|
|
174
|
+
name: APP_NAME,
|
|
175
|
+
platform: PLATFORM,
|
|
176
|
+
arch: ARCH,
|
|
177
|
+
electronVersion,
|
|
178
|
+
appVersion: VERSION.replace(/-.*/, ''),
|
|
179
|
+
icon: ICON,
|
|
180
|
+
asar: true,
|
|
181
|
+
prune: false,
|
|
182
|
+
junk: true,
|
|
183
|
+
extraResource: [SERVER_DIR, NODE_OUT],
|
|
184
|
+
...(WIN
|
|
185
|
+
? { win32metadata: { CompanyName: 'Six Degrees', ProductName: APP_NAME, FileDescription: APP_NAME, OriginalFilename: `${APP_NAME}.exe` } }
|
|
186
|
+
: { executableName: 'six-degrees' }),
|
|
187
|
+
});
|
|
188
|
+
const APP_DIR = path.join(OUT, WIN ? 'Six Degrees' : `six-degrees-${VERSION}-linux-x64`);
|
|
189
|
+
renameSync(built, APP_DIR);
|
|
190
|
+
console.log(` Electron ${electronVersion} (${PLATFORM} ${ARCH})`);
|
|
191
|
+
const RES = path.join(APP_DIR, 'resources');
|
|
192
|
+
|
|
193
|
+
// ---- 5. the scanner's Python (DESKTOP.md D2, D3) ------------------------------
|
|
194
|
+
const build = standaloneBuild(WIN ? 'win32-x64' : 'linux-x64');
|
|
195
|
+
const xy = build.version.split('.').slice(0, 2).join('.');
|
|
196
|
+
step(`Fetching Python ${build.version}`);
|
|
197
|
+
const pyArchive = await fetchPinned(build, 'Python');
|
|
198
|
+
const PY = path.join(RES, 'python');
|
|
199
|
+
rmSync(PY, { recursive: true, force: true });
|
|
200
|
+
run(TAR, ['-xzf', pyArchive, '-C', RES]);
|
|
201
|
+
const python = WIN ? path.join(PY, 'python.exe') : path.join(PY, 'bin', 'python3');
|
|
202
|
+
const SITE = WIN ? path.join(PY, 'Lib', 'site-packages') : path.join(PY, 'lib', `python${xy}`, 'site-packages');
|
|
203
|
+
// Only this Python's own packages and settings: nothing from the build machine's
|
|
204
|
+
// user site-packages, PYTHONPATH or pip configuration. Windows' Python can't start without SYSTEMROOT.
|
|
205
|
+
const pyEnv = WIN
|
|
206
|
+
? Object.fromEntries(Object.entries({
|
|
207
|
+
SYSTEMROOT: process.env.SYSTEMROOT, WINDIR: process.env.WINDIR, TEMP: process.env.TEMP, TMP: process.env.TMP,
|
|
208
|
+
USERPROFILE: process.env.USERPROFILE, LOCALAPPDATA: process.env.LOCALAPPDATA, APPDATA: process.env.APPDATA,
|
|
209
|
+
PATH: path.join(process.env.SYSTEMROOT || 'C:\\Windows', 'System32'),
|
|
210
|
+
PYTHONNOUSERSITE: '1', PYTHONDONTWRITEBYTECODE: '1', PYTHONUTF8: '1',
|
|
211
|
+
}).filter(([, v]) => v))
|
|
212
|
+
: { HOME: os.homedir(), TMPDIR: os.tmpdir(), PATH: '/usr/bin:/bin', LANG: 'C.UTF-8', PYTHONNOUSERSITE: '1', PYTHONDONTWRITEBYTECODE: '1' };
|
|
213
|
+
|
|
214
|
+
step('Installing the scanner\'s packages into it');
|
|
215
|
+
run(python, ['-E', '-s', '-m', 'pip', '--isolated', 'install', '--disable-pip-version-check', '--no-input',
|
|
216
|
+
'--no-warn-script-location', '--no-compile', '--index-url', 'https://pypi.org/simple',
|
|
217
|
+
'--cache-dir', path.join(CACHE, 'pip'), '--require-hashes', '--only-binary', ':all:',
|
|
218
|
+
'-r', path.join(ROOT, 'scripts', 'requirements.txt')], { env: pyEnv });
|
|
219
|
+
run(python, ['-E', '-s', '-m', 'pip', '--isolated', 'check', '--disable-pip-version-check'], { env: pyEnv });
|
|
220
|
+
|
|
221
|
+
step('Trimming what the scanner never uses');
|
|
222
|
+
const libDir = WIN ? path.join(PY, 'Lib') : path.join(PY, 'lib', `python${xy}`);
|
|
223
|
+
const remove = [
|
|
224
|
+
...['idlelib', 'tkinter', 'turtledemo', 'turtle.py', 'lib2to3', 'ensurepip', 'test'].map((n) => path.join(libDir, n)),
|
|
225
|
+
...readdirSync(SITE).filter((n) => n === 'pip' || /^pip-.*\.dist-info$/.test(n)).map((n) => path.join(SITE, n)),
|
|
226
|
+
...(WIN
|
|
227
|
+
? ['Scripts', 'include', 'libs', 'tcl', path.join('DLLs', '_tkinter.pyd'), path.join('DLLs', 'tcl86t.dll'), path.join('DLLs', 'tk86t.dll')].map((n) => path.join(PY, n))
|
|
228
|
+
: [
|
|
229
|
+
...['include', 'share', path.join('lib', 'pkgconfig')].map((n) => path.join(PY, n)),
|
|
230
|
+
...readdirSync(path.join(PY, 'lib')).filter((n) => /^(libtcl|libtk|tcl|tk|itcl|thread)/.test(n)).map((n) => path.join(PY, 'lib', n)),
|
|
231
|
+
...readdirSync(libDir).filter((n) => n.startsWith('config-')).map((n) => path.join(libDir, n)),
|
|
232
|
+
...readdirSync(path.join(libDir, 'lib-dynload')).filter((n) => n.startsWith('_tkinter.')).map((n) => path.join(libDir, 'lib-dynload', n)),
|
|
233
|
+
...readdirSync(path.join(PY, 'bin')).filter((n) => ![`python${xy}`, 'python3', 'python'].includes(n)).map((n) => path.join(PY, 'bin', n)),
|
|
234
|
+
]),
|
|
235
|
+
];
|
|
236
|
+
for (const p of remove) rmSync(p, { recursive: true, force: true });
|
|
237
|
+
for (const dir of [...findDirs(PY, (n) => n === '__pycache__'), ...findDirs(SITE, (n) => n === 'tests')]) rmSync(dir, { recursive: true, force: true });
|
|
238
|
+
|
|
239
|
+
// Linux: Playwright's driver carries its own Node; when it is byte for byte the
|
|
240
|
+
// app's, it becomes a relative link to it (as on the Mac). Not on Windows, where
|
|
241
|
+
// a symlink needs admin rights.
|
|
242
|
+
const driverNode = path.join(SITE, 'playwright', 'driver', WIN ? 'node.exe' : 'node');
|
|
243
|
+
if (!WIN && existsSync(driverNode) && sha256File(driverNode) === sha256File(path.join(RES, 'node'))) {
|
|
244
|
+
rmSync(driverNode);
|
|
245
|
+
symlinkSync(path.relative(path.dirname(driverNode), path.join(RES, 'node')), driverNode);
|
|
246
|
+
if (realpathSync(driverNode) !== realpathSync(path.join(RES, 'node'))) throw new Error('The driver\'s link to the app\'s Node is wrong.');
|
|
247
|
+
console.log(' Playwright runs on the app\'s own Node');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
step('Checking the Python inside the app');
|
|
251
|
+
// As the app runs it (lib/scanner-python.js OWN_PYTHON_FLAGS), with a "→" to
|
|
252
|
+
// prove its output is UTF-8 on Windows too.
|
|
253
|
+
const check = `${IMPORTS}; import sys, zoneinfo; zoneinfo.ZoneInfo('America/Los_Angeles'); from importlib.metadata import version as v; print('→ Python', sys.version.split()[0], '· Playwright', v('playwright'))`;
|
|
254
|
+
run(python, ['-E', '-s', '-B', '-u', '-X', 'utf8', '-c', check], { env: pyEnv });
|
|
255
|
+
run(driverNode, [path.join(SITE, 'playwright', 'driver', 'package', 'cli.js'), '--version'], { env: { ...pyEnv } });
|
|
256
|
+
for (const dir of findDirs(PY, (n) => n === '__pycache__')) rmSync(dir, { recursive: true, force: true });
|
|
257
|
+
|
|
258
|
+
// Windows' 260-character path limit: installed under
|
|
259
|
+
// C:\Users\<name>\AppData\Local\Programs\Six Degrees\ (about 70), the deepest file must still fit.
|
|
260
|
+
if (WIN) {
|
|
261
|
+
let longest = '';
|
|
262
|
+
for (const f of walk(APP_DIR)) { const rel = path.relative(APP_DIR, f); if (rel.length > longest.length) longest = rel; }
|
|
263
|
+
console.log(` longest path inside the app: ${longest.length} characters`);
|
|
264
|
+
if (longest.length > 170) throw new Error(`${longest} is too long for Windows once installed.`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---- 6. the packages ---------------------------------------------------------------
|
|
268
|
+
if (WIN) {
|
|
269
|
+
step('Building Setup.exe (Inno Setup)');
|
|
270
|
+
const iscc = [
|
|
271
|
+
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Inno Setup 6', 'ISCC.exe'),
|
|
272
|
+
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Inno Setup 6', 'ISCC.exe'),
|
|
273
|
+
].find((p) => existsSync(p));
|
|
274
|
+
if (!iscc) throw new Error('Inno Setup 6 (ISCC.exe) isn\'t installed.');
|
|
275
|
+
const numeric = `${VERSION.replace(/-.*/, '')}.0`;
|
|
276
|
+
run(iscc, ['/Qp', `/DAppVersion=${VERSION}`, `/DNumericVersion=${numeric}`, `/DSourceDir=${APP_DIR}`,
|
|
277
|
+
`/DOutputDir=${OUT}`, `/DIconFile=${ICON}`, path.join(ROOT, 'scripts', 'windows', 'six-degrees.iss')]);
|
|
278
|
+
const setup = path.join(OUT, `Six-Degrees-${VERSION}-win-x64-Setup.exe`);
|
|
279
|
+
if (!existsSync(setup)) throw new Error('Inno Setup didn\'t write Setup.exe.');
|
|
280
|
+
console.log(` ${path.relative(ROOT, setup)} (${Math.round(statSync(setup).size / 1e6)} MB)`);
|
|
281
|
+
} else {
|
|
282
|
+
step('Building the .tar.gz and the .deb');
|
|
283
|
+
// Readable by everyone, writable by the owner: packager's output folder is
|
|
284
|
+
// private (700, it's made as a temporary folder), and installed as root from
|
|
285
|
+
// the .deb that left /opt/six-degrees unreadable to the user who runs it.
|
|
286
|
+
run('chmod', ['-R', 'u+rwX,go+rX,go-w', APP_DIR]);
|
|
287
|
+
run('tar', ['-C', OUT, '-czf', path.join(OUT, `Six-Degrees-${VERSION}-linux-x64.tar.gz`), path.basename(APP_DIR)]);
|
|
288
|
+
const deb = path.join(STAGE, 'deb');
|
|
289
|
+
rmSync(deb, { recursive: true, force: true });
|
|
290
|
+
mkdirSync(path.join(deb, 'opt'), { recursive: true });
|
|
291
|
+
// cp -a, not cpSync: links stay relative (TRAPS §37).
|
|
292
|
+
run('cp', ['-a', APP_DIR, path.join(deb, 'opt', 'six-degrees')]);
|
|
293
|
+
// Electron's sandbox helper must be root's and setuid on Ubuntu 24.04 and later,
|
|
294
|
+
// or the app won't start (dpkg-deb --root-owner-group makes it root's).
|
|
295
|
+
chmodSync(path.join(deb, 'opt', 'six-degrees', 'chrome-sandbox'), 0o4755);
|
|
296
|
+
mkdirSync(path.join(deb, 'usr', 'bin'), { recursive: true });
|
|
297
|
+
symlinkSync('../../opt/six-degrees/six-degrees', path.join(deb, 'usr', 'bin', 'six-degrees'));
|
|
298
|
+
mkdirSync(path.join(deb, 'usr', 'share', 'applications'), { recursive: true });
|
|
299
|
+
writeFileSync(path.join(deb, 'usr', 'share', 'applications', 'six-degrees.desktop'), [
|
|
300
|
+
'[Desktop Entry]', 'Type=Application', 'Name=Six Degrees', 'Comment=See your LinkedIn network as a galaxy, on your own computer',
|
|
301
|
+
'Exec=/opt/six-degrees/six-degrees %U', 'Icon=six-degrees', 'Terminal=false', 'Categories=Office;Network;', 'StartupWMClass=Six Degrees', '',
|
|
302
|
+
].join('\n'));
|
|
303
|
+
mkdirSync(path.join(deb, 'usr', 'share', 'icons', 'hicolor', '512x512', 'apps'), { recursive: true });
|
|
304
|
+
cpSync(ICON, path.join(deb, 'usr', 'share', 'icons', 'hicolor', '512x512', 'apps', 'six-degrees.png'));
|
|
305
|
+
mkdirSync(path.join(deb, 'DEBIAN'), { recursive: true });
|
|
306
|
+
// A hyphen would be read as a Debian revision: 0.5.1-beta.1 is 0.5.1~beta.1 (which sorts before 0.5.1).
|
|
307
|
+
const debVersion = VERSION.replace('-', '~');
|
|
308
|
+
const kb = Number(execFileSync('du', ['-sk', path.join(deb, 'opt')]).toString().split(/\s+/)[0]);
|
|
309
|
+
writeFileSync(path.join(deb, 'DEBIAN', 'control'), [
|
|
310
|
+
'Package: six-degrees', `Version: ${debVersion}`, 'Architecture: amd64', 'Section: utils', 'Priority: optional',
|
|
311
|
+
'Maintainer: Six Degrees <noreply@sixdegreesapp.com>', 'Homepage: https://sixdegreesapp.com',
|
|
312
|
+
`Installed-Size: ${kb}`,
|
|
313
|
+
'Depends: libgtk-3-0t64 | libgtk-3-0, libnss3, libxss1, libxtst6, libasound2t64 | libasound2, libgbm1, libdrm2, xdg-utils',
|
|
314
|
+
'Recommends: google-chrome-stable',
|
|
315
|
+
'Description: See your LinkedIn network as a galaxy, on your own computer',
|
|
316
|
+
' Six Degrees maps the people you know and the people they know, ranks who can',
|
|
317
|
+
' introduce you to whom, and keeps all of it on this computer. Scanning drives',
|
|
318
|
+
' your own Google Chrome, which it needs for that part only.', '',
|
|
319
|
+
].join('\n'));
|
|
320
|
+
const debFile = path.join(OUT, `six-degrees_${debVersion}_amd64.deb`);
|
|
321
|
+
run('dpkg-deb', ['--root-owner-group', '-Zxz', '--build', deb, debFile]);
|
|
322
|
+
console.log(` ${path.relative(ROOT, debFile)} (${Math.round(statSync(debFile).size / 1e6)} MB)`);
|
|
323
|
+
}
|
|
324
|
+
rmSync(STAGE, { recursive: true, force: true });
|
|
325
|
+
console.log('\n✓ done');
|
|
@@ -24,7 +24,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import {
|
|
27
|
-
generate, fill, fullReleases, parseChangelog, changeCounts, highlights, longDate, countTests,
|
|
27
|
+
generate, fill, fullReleases, parseChangelog, changeCounts, highlights, longDate, countTests, anchor, releaseChartHtml,
|
|
28
28
|
} from './site-news.mjs';
|
|
29
29
|
import { markdown, frontMatter, htmlFrontMatter, escapeHtml, plainText } from './site-markdown.mjs';
|
|
30
30
|
|
|
@@ -36,7 +36,6 @@ const TEASERS = 3;
|
|
|
36
36
|
|
|
37
37
|
const read = (root, f) => readFileSync(path.join(root, f), 'utf8');
|
|
38
38
|
const shortDate = (iso) => longDate(iso).replace(/ (\w{3})\w* /, ' $1 ');
|
|
39
|
-
const anchor = (version) => `v${version}`;
|
|
40
39
|
|
|
41
40
|
// ---------------------------------------------------------------- loading
|
|
42
41
|
|
|
@@ -289,18 +288,82 @@ ${post.html}
|
|
|
289
288
|
}
|
|
290
289
|
|
|
291
290
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
291
|
+
* What came before 0.1.0, shown on /releases/ under "Before 0.1", newest first:
|
|
292
|
+
* the first build (June, "Alpha"), the static version (July, "Beta") and the
|
|
293
|
+
* open-source rebuild's first month (August and September, "Preview"), one
|
|
294
|
+
* entry for each day the work landed (Blake, 2026-10-02: showing it as one
|
|
295
|
+
* block "doesn't represent it properly"). Taken from each repository's commit
|
|
296
|
+
* history, with the dates the commits carry. They were never downloads, and
|
|
297
|
+
* the first two repositories are private, so nothing here links to them.
|
|
298
|
+
* Not in the releases feed: they aren't releases of this app.
|
|
299
|
+
*
|
|
300
|
+
* `commits` sizes each one's bar in the chart at the top of /releases/ (Blake,
|
|
301
|
+
* 2026-10-03: the chart began at "a really stacked 0.1.0"): the commits that
|
|
302
|
+
* landed for it, from the day after the one before up to its own, counted by
|
|
303
|
+
* the day in Florida, in that milestone's own repository (the first build's
|
|
304
|
+
* repository, the static version's, then this one). There was no changelog to
|
|
305
|
+
* count changes from yet.
|
|
295
306
|
*/
|
|
296
307
|
export const BEFORE = [
|
|
297
308
|
{
|
|
298
|
-
id: '
|
|
309
|
+
id: 'preview-6', commits: 14, stage: 'Preview', date: '2026-09-10', title: 'Preview 6: one-command updates',
|
|
310
|
+
text: 'Updating became one command that can\'t half-succeed, the app said when its server was older than its files, and the front page told the truth about how to run it.',
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: 'preview-5', commits: 23, stage: 'Preview', date: '2026-09-09', title: 'Preview 5: the scanner, end to end',
|
|
314
|
+
text: 'Who introduced whom was remembered, with a note when they accepted; each photo matched to the right person; the dial paged through every bridge; and the map said how much of the network was actually mapped.',
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
id: 'preview-4', commits: 4, stage: 'Preview', date: '2026-09-01', title: 'Preview 4: open source',
|
|
318
|
+
text: 'The rebuild went public on GitHub, and the earlier versions\' repositories went private.',
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'preview-3', commits: 4, stage: 'Preview', date: '2026-08-25', title: 'Preview 3: signing in first',
|
|
322
|
+
text: 'The scanner waited for you to sign in to LinkedIn yourself before reading anything, a first scan read your whole list, and loading the sample network could be undone.',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'preview-2', commits: 9, stage: 'Preview', date: '2026-08-24', title: 'Preview 2: one command to run it',
|
|
326
|
+
text: 'Packaged to run with one command, with an invented sample network and a real empty state, and two security holes closed (a cross-site write and stored script injection).',
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: 'preview-1', commits: 10, stage: 'Preview', date: '2026-08-21', title: 'Preview 1: the open-source rebuild begins',
|
|
330
|
+
text: 'This codebase starts: the hosted database replaced by a local one on your own computer, the scanner\'s last ties to the cloud cut, and checks that keep secrets and personal data out of the code.',
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: 'beta-3', commits: 2, stage: 'Beta', date: '2026-07-15', title: 'Beta 3: scanning from inside the app',
|
|
334
|
+
text: 'The scanner ran from the app itself, with its progress shown live, and one command started everything.',
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: 'beta-2', commits: 6, stage: 'Beta', date: '2026-07-13', title: 'Beta 2: local-first, for real',
|
|
338
|
+
text: 'A local database server on your own computer, a hardened scanner, and the first build\'s features brought back where they were safe for a demo.',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'beta-static', commits: 7, stage: 'Beta', date: '2026-07-12', title: 'Beta 1: the static version',
|
|
299
342
|
text: 'Your LinkedIn network as a galaxy, with leverage tiers, bridges and introduction paths. Local-first and entirely static: the first version where your data never left your device.',
|
|
300
343
|
},
|
|
301
344
|
{
|
|
302
|
-
id: 'alpha-
|
|
303
|
-
text: '
|
|
345
|
+
id: 'alpha-6', commits: 12, stage: 'Alpha', date: '2026-06-18', title: 'Alpha 6: on your phone, and a launch page',
|
|
346
|
+
text: 'A layout for phones, with a touch dial for spinning through a bridge\'s circle, and a showcase page for the launch.',
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: 'alpha-5', commits: 48, stage: 'Alpha', date: '2026-06-16', title: 'Alpha 5: chains',
|
|
350
|
+
text: 'The Chain view, where Bridge Chains began: hover a bridge to see their circle, and the people in it who could lead one step further.',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: 'alpha-4', commits: 66, stage: 'Alpha', date: '2026-06-15', title: 'Alpha 4: more than one network',
|
|
354
|
+
text: 'Each person\'s network kept apart from everyone else\'s, a bulk import, company scores of your own, and Paths reading companies from headlines.',
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: 'alpha-3', commits: 20, stage: 'Alpha', date: '2026-06-14', title: 'Alpha 3: who to add next',
|
|
358
|
+
text: 'A ranked list of who to add next (where Outlink began), a profile page with a level, points and milestones, and cluster cards you could open.',
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'alpha-2', commits: 25, stage: 'Alpha', date: '2026-06-12', title: 'Alpha 2: photos and clusters',
|
|
362
|
+
text: 'Profile photos on the map and the cards, the most valuable clusters ranked, and your bridges orbiting you by tier.',
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: 'alpha-first-build', commits: 58, stage: 'Alpha', date: '2026-06-11', title: 'Alpha 1: the first build',
|
|
366
|
+
text: '"6 Degrees of Separation": a LinkedIn network research tool with a force-directed D3 graph, hosted online, with a setup page and a launcher you could double-click. Where Six Degrees started.',
|
|
304
367
|
archive: 'https://six-degrees-linkedin.vercel.app/',
|
|
305
368
|
image: { src: '/img/first-build-june-2026.jpg', alt: 'The first build\'s welcome screen: 6 Degrees of Separation, Map your LinkedIn power network', width: 800, height: 500, caption: 'The first build, June 2026' },
|
|
306
369
|
},
|
|
@@ -309,7 +372,7 @@ export const BEFORE = [
|
|
|
309
372
|
export function beforeHtml() {
|
|
310
373
|
const entries = BEFORE.map((b) => ` <article class="release archive" id="${b.id}">
|
|
311
374
|
<header>
|
|
312
|
-
<h2><a href="#${b.id}">${escapeHtml(b.title)}</a> <span class="tag stage">${b.stage}</span></h2>
|
|
375
|
+
<h2><a href="#${b.id}">${escapeHtml(b.title)}</a> <span class="tag stage s-${b.stage.toLowerCase()}">${b.stage}</span></h2>
|
|
313
376
|
<p class="post-meta"><time datetime="${b.date}">${longDate(b.date)}</time></p>${b.archive ? `
|
|
314
377
|
<p class="release-links"><a href="${b.archive}">Archive: the original prototype (June 2026), kept as it was</a></p>` : ''}
|
|
315
378
|
</header>
|
|
@@ -318,8 +381,10 @@ export function beforeHtml() {
|
|
|
318
381
|
</article>`).join('\n');
|
|
319
382
|
return ` <section class="before" id="before-0-1" aria-labelledby="before-h">
|
|
320
383
|
<h2 id="before-h">Before 0.1</h2>
|
|
321
|
-
<p class="fine">
|
|
322
|
-
|
|
384
|
+
<p class="fine">Three and a half months before the first release, from the day each piece of work landed: the first
|
|
385
|
+
build in June (Alpha), the static version in July (Beta), and the open-source rebuild from
|
|
386
|
+
<time datetime="2026-08-21">21 August 2026</time> (Preview), which led to 0.1.0. Milestones taken from the
|
|
387
|
+
code's history, not downloads. <a href="/roadmap/#how-it-started">How it started</a>.</p>
|
|
323
388
|
${entries}
|
|
324
389
|
</section>`;
|
|
325
390
|
}
|
|
@@ -355,18 +420,23 @@ ${notes}
|
|
|
355
420
|
</div>
|
|
356
421
|
</article>`;
|
|
357
422
|
}).join('\n');
|
|
358
|
-
|
|
423
|
+
// The stages before 0.1 are in the list too, each at its newest milestone.
|
|
424
|
+
const stages = ['Preview', 'Beta', 'Alpha'].map((st) => BEFORE.find((b) => b.stage === st)).filter(Boolean);
|
|
425
|
+
const toc = `${full.map((r) => `<a href="#${anchor(r.version)}">${escapeHtml(r.version)}</a>`).join(' ')} <a href="#before-0-1">Before 0.1:</a> ${stages.map((b) => `<a class="stage-link s-${b.stage.toLowerCase()}" href="#${b.id}">${b.stage}</a>`).join(' ')}`;
|
|
359
426
|
const title = 'Release notes: every version of Six Degrees';
|
|
360
427
|
const description = `Everything added, changed and fixed in each of Six Degrees' ${full.length} releases, from ${full.at(-1).version} to ${full[0].version}, newest first.`;
|
|
361
428
|
const main = `${pageHead({ kicker: 'Releases', title: 'Release notes', lede: `Every version, newest first, from the <a href="${REPO}/blob/main/CHANGELOG.md">changelog</a>. Downloads for each are on <a href="${REPO}/releases">GitHub Releases</a>.` })}
|
|
362
429
|
<div class="panel chart-group releases-chart">
|
|
363
430
|
<div class="panel-head">
|
|
364
|
-
<h2>Every release,
|
|
365
|
-
<p class="fine">${values['change-count']} changes in ${values['release-count']} releases since ${values['first-release']}. Betas are counted in the release they became
|
|
431
|
+
<h2>Every release, and what came before</h2>
|
|
432
|
+
<p class="fine">${values['change-count']} changes in ${values['release-count']} releases since ${values['first-release']}. Betas are counted in the release they became.
|
|
433
|
+
Before that, ${BEFORE.length} milestones back to <time datetime="${BEFORE.at(-1).date}">${longDate(BEFORE.at(-1).date)}</time>: the first build (Alpha),
|
|
434
|
+
the static version (Beta) and the open-source rebuild (Preview), each by the commits that landed for it, as there was no changelog yet. Tap a bar for its notes.</p>
|
|
366
435
|
</div>
|
|
367
436
|
<div class="chart">
|
|
368
|
-
<div class="bars-box tall">${
|
|
437
|
+
<div class="bars-box tall">${releaseChartHtml(full, BEFORE)}</div>
|
|
369
438
|
<ul class="legend inline" role="list">${KINDS.slice(0, 5).map((k) => `<li class="k-${k}">${k[0].toUpperCase()}${k.slice(1)}</li>`).join('')}</ul>
|
|
439
|
+
<ul class="legend inline stages" role="list">${['Alpha', 'Beta', 'Preview'].map((s) => `<li class="k-${s.toLowerCase()}">${s}</li>`).join('')}<li class="note">before 0.1, in commits</li></ul>
|
|
370
440
|
</div>
|
|
371
441
|
</div>
|
|
372
442
|
<nav class="release-toc" aria-label="Versions"><span>Jump to</span> ${toc}</nav>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// A large, entirely invented network for checking the UI at scale, written
|
|
2
|
+
// straight into a scratch data folder with the app's own schema. Every name,
|
|
3
|
+
// company and profile link comes from the fixed word lists below: nobody real.
|
|
4
|
+
// Never point it at your own data folder (~/.six-degrees): it deletes the
|
|
5
|
+
// database in the folder you give it.
|
|
6
|
+
//
|
|
7
|
+
// node scripts/gen-test-network.mjs . /tmp/sd-test [--d1 1500] [--bridges 320] [--seed 7]
|
|
8
|
+
// SIX_DEGREES_HOME=/tmp/sd-test npx next dev -p 3457
|
|
9
|
+
//
|
|
10
|
+
// 1st degree, the first --bridges of them with a scanned circle (a few big ones,
|
|
11
|
+
// then 25 to 175 people), about one in seven 2nd-degree people found through a
|
|
12
|
+
// second bridge as well, eight company scans (3rd degree) and 3,000 ties between
|
|
13
|
+
// your connections.
|
|
14
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
15
|
+
import { mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
const [repo, home] = process.argv.slice(2);
|
|
19
|
+
if (!repo || !home) { console.error('Usage: node scripts/gen-test-network.mjs <repo> <scratch-folder> [--d1 N] [--bridges N] [--seed N]'); process.exit(2); }
|
|
20
|
+
if (path.resolve(home) === path.resolve(process.env.HOME || '', '.six-degrees')) { console.error('That is your real data folder. Pick a scratch folder.'); process.exit(2); }
|
|
21
|
+
const arg = (k, d) => { const i = process.argv.indexOf(k); return i > -1 ? Number(process.argv[i + 1]) : d; };
|
|
22
|
+
const D1 = arg('--d1', 1500), BRIDGES = arg('--bridges', 320), SEED = arg('--seed', 7);
|
|
23
|
+
const { SCHEMA_SQL } = await import(path.resolve(repo, 'db/schema.js'));
|
|
24
|
+
|
|
25
|
+
function rng(seed) { let a = seed >>> 0; return () => { a = (a + 0x6d2b79f5) >>> 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
|
|
26
|
+
const rand = rng(SEED);
|
|
27
|
+
const pick = (a) => a[Math.floor(rand() * a.length)];
|
|
28
|
+
|
|
29
|
+
const FIRST = 'Ada Bo Cleo Dev Esme Finn Gia Hugo Iris Jai Kira Lev Mina Nils Otto Pia Quinn Rune Sana Tobias Ula Vero Wren Xanthe Yuki Zane Amara Bodhi Cassia Dario Elio Freya Gideon Halle Ines Jonah Kaya Liora Marek Nadia Oona Paolo Rhea Soren Tamsin Ursa Viggo Wilder Ximena Yusuf Zora Callum Delphine Emeka Fiora Gustav Hana Idris Juno Kepler Linnea Mateo Noor'.split(' ');
|
|
30
|
+
const LAST = 'Ashworth Baptiste Calloway Delacroix Eriksen Farrow Gallagher Halvorsen Ibarra Jansson Kowalczyk Lindqvist Marchetti Nakamura Okonkwo Pereira Quintero Rasmussen Silvestri Thorne Ueda Vasquez Whitlock Ximenes Yamamoto Zabala Brennan Castellan Dumont Espinoza Fontaine Grimaldi Hollis Ingram Jokinen Kristiansen Lindgren Moreau Nyberg Ostrowski Petrov Rosales Stavros Tsegaye Varga Weatherby'.split(' ');
|
|
31
|
+
const PRE = 'Northwind Halcyon Verity Lumen Kestrel Meridian Orchard Tessellate Bright Harbor Ironwood Sable Cobalt Fernhill Ridgeline Aperture Quarry Blue Larch Everly Pinecrest Solace Amberly Quillon Vantor Brisk Ostara Calder Wynd Marlow Tamber Juniper Oberon Kinetic Sterling Driftwood Lanternfish Copperleaf Silvergate Redfern Hollow Crestline Bramble Mossbank'.split(' ');
|
|
32
|
+
const SUF = ['Labs','Health','Capital','Media','Foods','University','Consulting','Robotics','Studios','Energy','Realty','Analytics','Software','Bank','Logistics','Group','Partners','Ventures','Clinic','Games','Foundation','Systems','Retail','Law'];
|
|
33
|
+
const COMPANIES = [];
|
|
34
|
+
for (const p of PRE) for (const s of SUF) if (rand() < 0.55) COMPANIES.push(`${p} ${s}`);
|
|
35
|
+
// Zipf-ish: a few big employers, a long tail.
|
|
36
|
+
const weights = COMPANIES.map((_, i) => 1 / Math.pow(i + 1, 0.8));
|
|
37
|
+
const wTotal = weights.reduce((a, b) => a + b, 0);
|
|
38
|
+
function company() { let r = rand() * wTotal; for (let i = 0; i < COMPANIES.length; i++) if ((r -= weights[i]) < 0) return COMPANIES[i]; return COMPANIES[0]; }
|
|
39
|
+
const TITLES = ['Chief Executive Officer','Founder','Co-Founder','President','Chief Technology Officer','CFO','VP of Engineering','VP of Marketing','SVP of Operations','Managing Director','Partner','Director of Product','Director of Engineering','Head of Growth','Head of Design','Senior Engineering Manager','Group Product Manager','Engineering Manager','Principal Engineer','Staff Software Engineer','Lead Data Scientist','Senior Software Engineer','Senior Product Designer','Senior Manager, Partnerships','Account Manager','Client Partner','Brand Strategist','Software Engineer','Product Designer','Data Analyst','Program Coordinator','Registered Nurse','Associate Attorney','Recruiter','Marketing Intern','Research Assistant','Student'];
|
|
40
|
+
function headline(title, co) {
|
|
41
|
+
const r = rand();
|
|
42
|
+
if (!co) return title;
|
|
43
|
+
if (r < 0.55) return `${title} at ${co}`;
|
|
44
|
+
if (r < 0.7) return `${title} @ ${co} | Building the future of ${pick(['health','payments','media','AI','logistics'])}`;
|
|
45
|
+
if (r < 0.82) return `${title} | ${co} | ex-${company()}`;
|
|
46
|
+
if (r < 0.92) return `${title}, ${co}`;
|
|
47
|
+
return `${title} at ${co} · Former ${pick(TITLES)} at ${company()}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dbFile = path.join(home, 'six-degrees.sqlite');
|
|
51
|
+
mkdirSync(home, { recursive: true });
|
|
52
|
+
for (const f of [dbFile, dbFile + '-wal', dbFile + '-shm']) if (existsSync(f)) rmSync(f);
|
|
53
|
+
const db = new DatabaseSync(dbFile);
|
|
54
|
+
db.exec(SCHEMA_SQL);
|
|
55
|
+
const userId = 'perf-user-0001';
|
|
56
|
+
db.prepare('INSERT INTO users (id, name) VALUES (?, ?)').run(userId, 'You');
|
|
57
|
+
const ins = db.prepare(`INSERT INTO linkedin_connections (id, degree, source_connection_id, name, headline, company, role, profile_url, profile_image_url, mutual_count, scanned_company, user_id, unlock_status, influence_signals)
|
|
58
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, '{}')`);
|
|
59
|
+
let n = 0;
|
|
60
|
+
const person = (degree, source, scanned = null) => {
|
|
61
|
+
n++;
|
|
62
|
+
const name = `${pick(FIRST)} ${pick(LAST)}`;
|
|
63
|
+
const co = rand() < 0.06 ? '' : (scanned || company());
|
|
64
|
+
const title = pick(TITLES);
|
|
65
|
+
return { id: `perf-${degree}-${n}`, degree, source, name, headline: headline(title, co), company: co || null, role: title,
|
|
66
|
+
url: `https://www.linkedin.com/in/${name.toLowerCase().replace(/[^a-z]+/g, '-')}-${n.toString(36)}`, scanned };
|
|
67
|
+
};
|
|
68
|
+
const put = (p) => ins.run(p.id, p.degree, p.source, p.name, p.headline, p.company, p.role, p.url, p.degree === 2 ? Math.floor(rand() * 40) : null, p.scanned, userId, p.degree === 1 ? 'unlocked' : 'locked');
|
|
69
|
+
|
|
70
|
+
db.exec('BEGIN');
|
|
71
|
+
const d1 = Array.from({ length: D1 }, () => person(1, null));
|
|
72
|
+
d1.forEach(put);
|
|
73
|
+
const bridges = d1.slice(0, BRIDGES);
|
|
74
|
+
const d2 = [];
|
|
75
|
+
for (const b of bridges) {
|
|
76
|
+
const size = [980, 950, 500, 310, 260, 180][bridges.indexOf(b)] ?? (25 + Math.floor(rand() * 150));
|
|
77
|
+
for (let i = 0; i < size; i++) { const p = person(2, b.id); put(p); d2.push(p); }
|
|
78
|
+
}
|
|
79
|
+
// About one in seven 2nd-degree people is known by a second bridge: same profile, another row.
|
|
80
|
+
let shared = 0;
|
|
81
|
+
for (const p of d2.slice()) {
|
|
82
|
+
if (rand() >= 0.15) continue;
|
|
83
|
+
const b = pick(bridges); if (b.id === p.source) continue;
|
|
84
|
+
put({ ...p, id: `${p.id}-via`, source: b.id }); shared++;
|
|
85
|
+
}
|
|
86
|
+
// A few company scans (3rd+).
|
|
87
|
+
let d3 = 0;
|
|
88
|
+
for (const co of COMPANIES.slice(0, 8)) for (let i = 0; i < 50; i++) { put(person(3, null, co)); d3++; }
|
|
89
|
+
// Ties between your own connections.
|
|
90
|
+
const tie = db.prepare('INSERT OR IGNORE INTO connection_ties (id, user_id, a_url, b_url) VALUES (?, ?, ?, ?)');
|
|
91
|
+
for (let i = 0; i < 3000; i++) { const [a, b] = [pick(d1).url, pick(d1).url].sort(); if (a !== b) tie.run(`t${i}`, userId, a, b); }
|
|
92
|
+
db.exec('COMMIT');
|
|
93
|
+
console.log(JSON.stringify({ home, companies: COMPANIES.length, d1: d1.length, d2: d2.length + shared, d2People: d2.length, shared, d3 }));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Run the Next CLI with this project's settings, on any OS.
|
|
3
|
+
//
|
|
4
|
+
// package.json used to say `NEXT_TELEMETRY_DISABLED=1 next build`, which is
|
|
5
|
+
// shell syntax Windows does not have — the Windows build failed on exactly
|
|
6
|
+
// that line. Same settings, set here instead:
|
|
7
|
+
// NEXT_TELEMETRY_DISABLED=1 always
|
|
8
|
+
// SIX_DEGREES_BIND=127.0.0.1 for dev and start (only this machine)
|
|
9
|
+
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const env = { ...process.env, NEXT_TELEMETRY_DISABLED: '1' };
|
|
15
|
+
if (['dev', 'start'].includes(args[0])) env.SIX_DEGREES_BIND ??= '127.0.0.1';
|
|
16
|
+
|
|
17
|
+
const next = createRequire(import.meta.url).resolve('next/dist/bin/next');
|
|
18
|
+
const child = spawn(process.execPath, [next, ...args], { stdio: 'inherit', env });
|
|
19
|
+
child.on('exit', (code, signal) => process.exit(signal ? 1 : code ?? 1));
|
|
20
|
+
for (const sig of ['SIGINT', 'SIGTERM']) process.on(sig, () => child.kill(sig));
|
|
@@ -38,6 +38,9 @@ ARROW = """<svg class="arrow" viewBox="0 0 24 24" width="30" height="30" aria-hi
|
|
|
38
38
|
BUTTONS = [
|
|
39
39
|
("download-apple-silicon.png", "Click to download for Mac", "Apple Silicon", "M1 or newer", "green"),
|
|
40
40
|
("download-intel.png", "Click to download for Mac", "Intel", "Macs with an Intel chip", "slate"),
|
|
41
|
+
# Windows and Linux (DESKTOP.md D3): a beta, on its own pre-release.
|
|
42
|
+
("download-windows.png", "Click to download · beta", "Windows", "Windows 10 or 11, one click", "slate"),
|
|
43
|
+
("download-linux.png", "Click to download · beta", "Linux", "Ubuntu and Debian · .deb", "slate"),
|
|
41
44
|
]
|
|
42
45
|
|
|
43
46
|
CSS = """
|