@lampdotdev/lamp 0.0.0-stage → 0.36.2-nightly.16
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/LICENSE +21 -0
- package/README.md +397 -2
- package/Resources/Devices/roku.json +410 -0
- package/Resources/Devices/xbox-one.json +367 -0
- package/Resources/GameKit/NOTICE.md +63 -0
- package/Resources/GameKit/apple/LampPage.swift +1813 -0
- package/Resources/GameKit/apple/LampPlayer.swift +46 -0
- package/Resources/GameKit/apple/LampWebApp.swift +63 -0
- package/Resources/GameKit/apple/LampWebGame.swift +87 -0
- package/Resources/GameKit/apple/Native.swift +12 -0
- package/Resources/GameKit/build.sh +55 -0
- package/Resources/GameKit/lamp_game.h +116 -0
- package/Resources/GameKit/lamp_host.h +19 -0
- package/Resources/GameKit/lamp_kit.cpp +61 -0
- package/Resources/GameKit/lamp_sound.cpp +455 -0
- package/Resources/GameKit/lamp_xbox.cpp +374 -0
- package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
- package/Resources/GameKit/libraries/cgltf.h +7240 -0
- package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
- package/Resources/GameKit/libraries/dr_wav.h +9209 -0
- package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
- package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
- package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
- package/Resources/GameKit/libraries/sokol_log.h +335 -0
- package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
- package/Resources/GameKit/libraries/stb_image.h +7988 -0
- package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
- package/Resources/GameKit/page/lamp-live.js +672 -0
- package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
- package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
- package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
- package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
- package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
- package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
- package/Resources/GameKit/web/lamp-game.js +3042 -0
- package/Resources/GameKit/web/lamp-phaser.js +128 -0
- package/Resources/WebHost/App.exe +0 -0
- package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
- package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
- package/Resources/WebHost/lamp_controllers.h +1506 -0
- package/Resources/WebHost/lamp_web_host.cpp +1173 -0
- package/Resources/WebLibraries/earlier.txt +318 -0
- package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
- package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
- package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
- package/Resources/WebLibraries/qrcode/LICENSE +21 -0
- package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
- package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
- package/Resources/WebLibraries/rapier/LICENSE +201 -0
- package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
- package/Resources/WebLibraries/rapier/rapier.js +2 -0
- package/Resources/WebLibraries/three/LICENSE +21 -0
- package/Resources/WebLibraries/three/NOTICE.md +4 -0
- package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
- package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
- package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
- package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
- package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
- package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
- package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
- package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
- package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
- package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
- package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
- package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
- package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
- package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
- package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
- package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
- package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
- package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
- package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
- package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
- package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
- package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
- package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
- package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
- package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
- package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
- package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
- package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
- package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
- package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
- package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
- package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
- package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
- package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
- package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
- package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
- package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
- package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
- package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
- package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
- package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
- package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
- package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
- package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
- package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
- package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
- package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
- package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
- package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
- package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
- package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
- package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
- package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
- package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
- package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
- package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
- package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
- package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
- package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
- package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
- package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
- package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
- package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
- package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
- package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
- package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
- package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
- package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
- package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
- package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
- package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
- package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
- package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
- package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
- package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
- package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
- package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
- package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
- package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
- package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
- package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
- package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
- package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
- package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
- package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
- package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
- package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
- package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
- package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
- package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
- package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
- package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
- package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
- package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
- package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
- package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
- package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
- package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
- package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
- package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
- package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
- package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
- package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
- package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
- package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
- package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
- package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
- package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
- package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
- package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
- package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
- package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
- package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
- package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
- package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
- package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
- package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
- package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
- package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
- package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
- package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
- package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
- package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
- package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
- package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
- package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
- package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
- package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
- package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
- package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
- package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
- package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
- package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
- package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
- package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
- package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
- package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
- package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
- package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
- package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
- package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
- package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
- package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
- package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
- package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
- package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
- package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
- package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
- package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
- package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
- package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
- package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
- package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
- package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
- package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
- package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
- package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
- package/Resources/WebLibraries/three/three.core.js +61162 -0
- package/Resources/WebLibraries/three/three.module.js +19537 -0
- package/Resources/WebLibraries/three/three.tsl.js +41903 -0
- package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
- package/Resources/share-rules.json +64 -0
- package/Resources/skills/2d-game/SKILL.md +305 -0
- package/Resources/skills/3d-game/SKILL.md +274 -0
- package/Resources/skills/accounts/SKILL.md +41 -0
- package/Resources/skills/android-design/SKILL.md +159 -0
- package/Resources/skills/app-ai/SKILL.md +154 -0
- package/Resources/skills/app-intents/SKILL.md +31 -0
- package/Resources/skills/app-kinds/SKILL.md +122 -0
- package/Resources/skills/apple-watch/SKILL.md +43 -0
- package/Resources/skills/assets/SKILL.md +183 -0
- package/Resources/skills/building-blocks/SKILL.md +97 -0
- package/Resources/skills/device-control/SKILL.md +56 -0
- package/Resources/skills/device-integrations/SKILL.md +278 -0
- package/Resources/skills/finder-files/SKILL.md +19 -0
- package/Resources/skills/game/SKILL.md +416 -0
- package/Resources/skills/game-3d/SKILL.md +12 -0
- package/Resources/skills/game-art/SKILL.md +11 -0
- package/Resources/skills/game-feel/SKILL.md +13 -0
- package/Resources/skills/game-levels/SKILL.md +12 -0
- package/Resources/skills/game-live/SKILL.md +12 -0
- package/Resources/skills/game-physics/SKILL.md +13 -0
- package/Resources/skills/game-playtest/SKILL.md +26 -0
- package/Resources/skills/game-ui/SKILL.md +278 -0
- package/Resources/skills/github-projects/SKILL.md +62 -0
- package/Resources/skills/icloud-sync/SKILL.md +30 -0
- package/Resources/skills/iphone-hardware/SKILL.md +57 -0
- package/Resources/skills/iphone-widgets/SKILL.md +53 -0
- package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
- package/Resources/skills/mac-widgets/SKILL.md +60 -0
- package/Resources/skills/maps/SKILL.md +8 -0
- package/Resources/skills/menu-bar/SKILL.md +36 -0
- package/Resources/skills/more-apps/SKILL.md +53 -0
- package/Resources/skills/phone-controllers/SKILL.md +79 -0
- package/Resources/skills/phone-pages/SKILL.md +173 -0
- package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
- package/Resources/skills/scene-3d/SKILL.md +27 -0
- package/Resources/skills/scripting/SKILL.md +17 -0
- package/Resources/skills/share-extension/SKILL.md +22 -0
- package/Resources/skills/tv-design/SKILL.md +72 -0
- package/Resources/skills/web-app/SKILL.md +170 -0
- package/Resources/skills/web-game/SKILL.md +95 -0
- package/Resources/skills/web-or-native/SKILL.md +108 -0
- package/Resources/skills/wish-links/SKILL.md +156 -0
- package/THIRD_PARTY_NOTICES.md +170 -0
- package/cli/ai.mjs +237 -0
- package/cli/android.mjs +941 -0
- package/cli/app-ai.mjs +473 -0
- package/cli/browser.mjs +284 -0
- package/cli/calls.mjs +594 -0
- package/cli/dev.mjs +820 -0
- package/cli/devices.mjs +984 -0
- package/cli/lamp.mjs +1326 -0
- package/cli/lampapp.mjs +332 -0
- package/cli/lamps/android-host/LampPageGateway.kt +345 -0
- package/cli/lamps/android-host/app/build.gradle.kts +40 -0
- package/cli/lamps/android-host/build.gradle.kts +4 -0
- package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
- package/cli/lamps/android-host/game/MainActivity.kt +173 -0
- package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
- package/cli/lamps/android-host/gradle.properties +8 -0
- package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
- package/cli/lamps/android-host/page/MainActivity.kt +193 -0
- package/cli/lamps/android-host/settings.gradle.kts +30 -0
- package/cli/lamps/android-host/tools.json +43 -0
- package/cli/lamps/lamp-controllers.js +2 -0
- package/cli/lamps/lamp-for-games.js +6922 -0
- package/cli/lamps/lamp.js +7082 -0
- package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
- package/cli/lamps/mac-starter/APP.md +12 -0
- package/cli/lamps/mac-starter/Package.swift +8 -0
- package/cli/lamps/mac-starter/Sources/App.swift +11 -0
- package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
- package/cli/lamps/mac-starter/Sources/LampSupport.swift +18215 -0
- package/cli/lamps/mac-starter/build.sh +336 -0
- package/cli/lamps/mac-starter/gitignore +3 -0
- package/cli/lamps/mac-starter/lamp-starter.json +6 -0
- package/cli/lamps/web-starter/APP.md +8 -0
- package/cli/lamps/web-starter/app.js +1 -0
- package/cli/lamps/web-starter/index.html +17 -0
- package/cli/lamps/web-starter/lamp-starter.json +5 -0
- package/cli/lamps/web-starter/lamp.js +7082 -0
- package/cli/lamps/web-starter/style.css +25 -0
- package/cli/link.mjs +202 -0
- package/cli/local-ai.mjs +193 -0
- package/cli/mac.mjs +621 -0
- package/cli/mdns.mjs +205 -0
- package/cli/msix.mjs +413 -0
- package/cli/network.mjs +209 -0
- package/cli/openrouter.mjs +133 -0
- package/cli/outside.mjs +203 -0
- package/cli/page.js +317 -0
- package/cli/place.mjs +298 -0
- package/cli/png.mjs +184 -0
- package/cli/share.mjs +270 -0
- package/cli/skill/SKILL.md +280 -0
- package/cli/skills.mjs +71 -0
- package/cli/state.mjs +176 -0
- package/cli/update.mjs +162 -0
- package/package.json +20 -4
- package/site/lib/quota.js +59 -0
- package/site/lib/shared-wishes.js +1727 -0
package/cli/mac.mjs
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
// An app Lamp made in Swift, built and run on this Mac from its folder, with Lamp's app closed. The folder holds all
|
|
2
|
+
// it takes: its own build script (build.sh, which Lamp writes) builds build/<Name>.app, and the app's support code
|
|
3
|
+
// (Sources/LampSupport.swift) reads where Lamp is from values in its Info.plist (`LampInstall`, which Lamp's app
|
|
4
|
+
// writes into every copy it installs: `Template.installValues`). Here lamp writes those values, so the app takes lamp
|
|
5
|
+
// for its Lamp: it keeps its log and pictures of its own windows in the folder's .lamp/runtime, and takes a picture
|
|
6
|
+
// when lamp asks it to, with no permission asked of anyone (an app may picture its own windows).
|
|
7
|
+
//
|
|
8
|
+
// A saved change builds the app again and opens it again. Putting a change into the app while it runs is Lamp's
|
|
9
|
+
// app's still (`LiveReload`): `lamp open --in-lamp` hands the folder to it (outside.mjs).
|
|
10
|
+
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { randomBytes, createHash, createHmac } from "node:crypto";
|
|
13
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
14
|
+
import { watch, existsSync, statSync, mkdirSync, appendFileSync, writeFileSync, readFileSync, readdirSync, rmSync, copyFileSync, openSync, readSync, closeSync, cpSync, realpathSync, readlinkSync, chmodSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { basename, dirname, join, sep } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { Problem, manifestOf, projectOf, stateOf, serverFileOf, firstPortOf } from "./state.mjs";
|
|
19
|
+
|
|
20
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
21
|
+
|
|
22
|
+
/** Whether the folder is an app Lamp made in Swift for the Mac: its package, and the build script Lamp writes. */
|
|
23
|
+
export const isMacApp = (folder) => existsSync(join(folder, "Package.swift")) && existsSync(join(folder, "build.sh")) && existsSync(join(folder, "Sources"));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* lamp's moves as a Mac app's driver takes them (`LampDriver`, the actions of Lamp's `use_app`), for lamp's own copy
|
|
27
|
+
* of the app and for one Lamp's app has open alike.
|
|
28
|
+
*/
|
|
29
|
+
export function macActions(moves) {
|
|
30
|
+
const number = (value, what) => { const n = Number(value); if (!Number.isFinite(n)) throw new Problem(what); return n; };
|
|
31
|
+
return moves.map((move) => {
|
|
32
|
+
if (move.kind === "click") return move.target ? { action: "click", target: String(move.target) } : { action: "click", x: number(move.x, "click takes a point of the picture, or a name from the screen as text"), y: number(move.y, "click takes a point of the picture, or a name from the screen as text") };
|
|
33
|
+
if (move.kind === "type") return { action: "type", text: String(move.text ?? "") };
|
|
34
|
+
if (move.kind === "key") return { action: "key", key: String(move.key), ...(Number(move.seconds) ? { amount: Math.min(Number(move.seconds), 10) } : {}) };
|
|
35
|
+
if (move.kind === "drag") return { action: "drag", x: number(move.x, "drag takes where it starts and ends"), y: number(move.y, "drag takes where it starts and ends"), to_x: number(move.toX, "drag takes where it starts and ends"), to_y: number(move.toY, "drag takes where it starts and ends") };
|
|
36
|
+
if (move.kind === "scroll") return { action: "scroll", amount: number(move.points, "scroll takes how far, down being positive"), ...(Number.isFinite(Number(move.x)) && Number.isFinite(Number(move.y)) ? { x: Number(move.x), y: Number(move.y) } : {}) };
|
|
37
|
+
if (move.kind === "wait") return { action: "wait", amount: Math.min(Math.max(Number(move.seconds) || 1, 0), 30) };
|
|
38
|
+
throw new Problem("a Mac app takes click, type, key, drag, scroll and wait: a stick and buttons are a game's");
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// MARK: Lamp's own files
|
|
43
|
+
|
|
44
|
+
const STARTER = join(dirname(fileURLToPath(import.meta.url)), "lamps", "mac-starter");
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A Mac app's starting files as Lamp's app makes them (`Template.scaffold`), from a copy Lamp's own code wrote under a
|
|
48
|
+
* stand-in name (scripts/cli-files.sh), named `title` (letters, digits and spaces). With `only`, just those of them
|
|
49
|
+
* (Lamp's own: Package.swift, build.sh, Sources/LampSupport.swift), each only where the folder has none: an app that
|
|
50
|
+
* came in a shared file, which carries none of Lamp's files, gets this Lamp's before it builds.
|
|
51
|
+
*/
|
|
52
|
+
export function writeMacStarter(folder, title, { only = null } = {}) {
|
|
53
|
+
if (!existsSync(join(STARTER, "lamp-starter.json"))) {
|
|
54
|
+
throw new Problem(`Lamp's starting files for a Mac app aren't made yet. In Lamp's folder, run scripts/cli-files.sh: it writes them from Lamp's own code.`);
|
|
55
|
+
}
|
|
56
|
+
const stood = JSON.parse(readFileSync(join(STARTER, "lamp-starter.json"), "utf8"));
|
|
57
|
+
// The program's name is the one the package already gives it, where it has one, so the script builds what's there.
|
|
58
|
+
const given = (() => { try { return /name:\s*"([A-Za-z][A-Za-z0-9]*)"/.exec(readFileSync(join(folder, "Package.swift"), "utf8"))?.[1]; } catch { return null; } })();
|
|
59
|
+
const exe = given ?? title.split(/\s+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join(""), slug = exe.toLowerCase();
|
|
60
|
+
const named = (text) => text.replaceAll(stood.bundle, `dev.lamp.cli.app.${slug}`).replaceAll(stood.name, title).replaceAll(stood.exe, exe).replaceAll(stood.slug, slug);
|
|
61
|
+
const copy = (from, to, at = "") => {
|
|
62
|
+
mkdirSync(to, { recursive: true });
|
|
63
|
+
for (const entry of readdirSync(from, { withFileTypes: true })) {
|
|
64
|
+
// What Lamp's app knows of the stand-in (.lamp) and the names it stood for aren't the new app's.
|
|
65
|
+
if (entry.name === ".lamp" || entry.name === "lamp-starter.json") continue;
|
|
66
|
+
// A starter from npm carries its .gitignore as gitignore, since npm leaves the other name out of a package.
|
|
67
|
+
const as = at === "" && entry.name === "gitignore" ? ".gitignore" : named(entry.name);
|
|
68
|
+
const source = join(from, entry.name), path = at + as, target = join(to, as);
|
|
69
|
+
if (entry.isDirectory()) { copy(source, target, path + "/"); continue; }
|
|
70
|
+
if (!entry.isFile() || (only && (!only.includes(path) || existsSync(target)))) continue;
|
|
71
|
+
// Lamp's building blocks are the same for every app; the rest names this one.
|
|
72
|
+
if (entry.name === "LampSupport.swift" || !/\.(swift|sh|md|json|plist|txt)$|^\.gitignore$/.test(entry.name)) cpSync(source, target);
|
|
73
|
+
else writeFileSync(target, named(readFileSync(source, "utf8")));
|
|
74
|
+
chmodSync(target, statSync(source).mode);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
copy(STARTER, folder);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A name Lamp's starting files can carry into a build script and code: letters, digits and spaces, a letter first. */
|
|
81
|
+
export function safeTitle(name) {
|
|
82
|
+
const words = String(name ?? "").normalize("NFKD").replace(/[^A-Za-z0-9 ]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
83
|
+
const title = words.join(" ").replace(/^[0-9 ]+/, "").slice(0, 40).trim();
|
|
84
|
+
return title || "App";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// MARK: The build's sandbox
|
|
88
|
+
//
|
|
89
|
+
// The build runs what's in the folder: its script, its package (which SwiftPM runs as a program before it builds
|
|
90
|
+
// anything) and whatever its code does as it compiles. Lamp's app runs it under sandbox-exec with a profile that
|
|
91
|
+
// closes every person's files and opens the app's own folder and what a build needs (`Sandbox.profile`); this is that
|
|
92
|
+
// profile, for a build. Later rules win, so the order is the meaning.
|
|
93
|
+
|
|
94
|
+
const real = (path) => { try { return realpathSync.native(path); } catch { return path; } };
|
|
95
|
+
const quotedPath = (path) => JSON.stringify(path);
|
|
96
|
+
|
|
97
|
+
/** The Xcode or command line tools in use, wherever they are: read by every compile, never written. */
|
|
98
|
+
function developerFolders() {
|
|
99
|
+
const given = [process.env.DEVELOPER_DIR, (() => { try { return readlinkSync("/var/db/xcode_select_link"); } catch { return null; } })()];
|
|
100
|
+
const folders = [];
|
|
101
|
+
for (const folder of given) {
|
|
102
|
+
if (!folder?.startsWith("/")) continue;
|
|
103
|
+
const path = real(folder), at = path.indexOf(".app/");
|
|
104
|
+
const bundle = at < 0 ? path : path.slice(0, at + 4);
|
|
105
|
+
if (bundle !== "/" && !folders.includes(bundle)) folders.push(bundle);
|
|
106
|
+
}
|
|
107
|
+
return folders;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildProfile(app, bin) {
|
|
111
|
+
const home = real(homedir());
|
|
112
|
+
const lines = [
|
|
113
|
+
"(version 1)",
|
|
114
|
+
"(allow default)",
|
|
115
|
+
'(deny file-read* file-write* (subpath "/Users") (subpath "/Volumes") (subpath "/Network") (subpath "/private/var/root"))',
|
|
116
|
+
'(deny file-read* file-write* (subpath "/Library/Application Support") (subpath "/Library/Keychains"))',
|
|
117
|
+
`(deny file-read* file-write* (subpath ${quotedPath(home)}))`,
|
|
118
|
+
// What a build needs from the home folder: the toolchain, package caches, git's settings. Read only, apart from the caches.
|
|
119
|
+
`(allow file-read* (subpath ${quotedPath(home + "/Library/Developer")}) (subpath ${quotedPath(home + "/.swiftpm")}) (literal ${quotedPath(home + "/.gitconfig")}) (subpath ${quotedPath(home + "/.config/git")}))`,
|
|
120
|
+
`(allow file-read* file-write* (subpath ${quotedPath(home + "/Library/Caches")}) (subpath ${quotedPath(home + "/Library/org.swift.swiftpm")}) (subpath "/private/tmp") (subpath "/tmp"))`,
|
|
121
|
+
];
|
|
122
|
+
if (process.env.TMPDIR) lines.push(`(allow file-read* file-write* (subpath ${quotedPath(real(process.env.TMPDIR))}))`);
|
|
123
|
+
const developer = developerFolders();
|
|
124
|
+
for (const folder of developer) {
|
|
125
|
+
lines.push(`(allow file-read* (subpath ${quotedPath(folder)}))`);
|
|
126
|
+
for (let path = dirname(folder); path !== "/"; path = dirname(path)) lines.push(`(allow file-read-metadata (literal ${quotedPath(path)}))`);
|
|
127
|
+
}
|
|
128
|
+
// The app's own folder opens, by its own name.
|
|
129
|
+
lines.push(`(allow file-read* file-write* (subpath ${quotedPath(app)}))`);
|
|
130
|
+
// lamp's \`swift\`, first on the build's path, is read and run from where lamp keeps it; nothing else of lamp's state opens.
|
|
131
|
+
if (bin) {
|
|
132
|
+
lines.push(`(allow file-read* (subpath ${quotedPath(real(bin))}))`);
|
|
133
|
+
for (let path = dirname(real(bin)); path !== "/"; path = dirname(path)) lines.push(`(allow file-read-metadata (literal ${quotedPath(path)}))`);
|
|
134
|
+
}
|
|
135
|
+
// Where programs are found and run from stays as it is: nothing the build leaves there is run by anyone later.
|
|
136
|
+
lines.push(`(deny file-write* (subpath ${quotedPath(real("/opt/homebrew"))}) (subpath ${quotedPath(real("/usr/local"))}))`);
|
|
137
|
+
lines.push(`(deny file-write* ${[real("/Applications"), ...developer].map((path) => `(subpath ${quotedPath(path)})`).join(" ")})`);
|
|
138
|
+
// The folders on the way to the app's can be looked at, not read: a program resolving a path looks at every step.
|
|
139
|
+
for (let path = dirname(app); path.startsWith(home) && path.length >= home.length; path = dirname(path)) {
|
|
140
|
+
lines.push(`(allow file-read-metadata (literal ${quotedPath(path)}))`);
|
|
141
|
+
if (path === home) break;
|
|
142
|
+
}
|
|
143
|
+
lines.push(`(allow file-read-metadata (literal ${quotedPath(dirname(home))}))`);
|
|
144
|
+
// Xcode's helpers that stay running are found through shared memory, whoever started them: each build starts its own.
|
|
145
|
+
lines.push('(deny ipc-posix-shm* (ipc-posix-name-regex #"^/?ib"))');
|
|
146
|
+
return lines.join("\n") + "\n";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// SwiftPM runs its builds under a sandbox of its own, which macOS won't start inside another: this `swift`, put first
|
|
150
|
+
// on the build's path, hands its build commands --disable-sandbox (the build is fenced in already) and passes
|
|
151
|
+
// everything else to the real one. Lamp's own (`Sandbox.swiftShim`).
|
|
152
|
+
const SWIFT_SHIM = `#!/bin/bash
|
|
153
|
+
# Written by lamp. SwiftPM's own sandbox can't start inside lamp's, which already confines the build.
|
|
154
|
+
here="$(cd "$(dirname "$0")" && pwd)"
|
|
155
|
+
rest="$(printf '%s' "$PATH" | tr ':' '\\n' | grep -vx "$here" | paste -sd: -)"
|
|
156
|
+
real="$(PATH="$rest" command -v swift)"
|
|
157
|
+
case "$1" in
|
|
158
|
+
build|test|run|package) exec "$real" "$1" --disable-sandbox "\${@:2}" ;;
|
|
159
|
+
*) exec "$real" "$@" ;;
|
|
160
|
+
esac
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
/** Says something to every app listening for it on this Mac (a distributed notification), as Lamp's app does. */
|
|
164
|
+
function post(name, info = null) {
|
|
165
|
+
spawnSync("/usr/bin/osascript", ["-l", "JavaScript", "-e",
|
|
166
|
+
`ObjC.import("Foundation"); $.NSDistributedNotificationCenter.defaultCenter.postNotificationNameObjectUserInfoDeliverImmediately(${JSON.stringify(name)}, $(), ${info ? `$(${JSON.stringify(info)})` : "$()"}, true)`],
|
|
167
|
+
{ stdio: "ignore", timeout: 10_000 });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Starts building and running the app in the folder. `quiet` prints nothing; `idle` (minutes) ends a server nothing
|
|
172
|
+
* has used for that long, which one started by `lamp look` is given.
|
|
173
|
+
*/
|
|
174
|
+
export async function serveMac({ folder, port: asked, quiet = false, idle = 0 } = {}) {
|
|
175
|
+
if (process.platform !== "darwin") throw new Problem("an app made in Swift for the Mac is built and run on a Mac");
|
|
176
|
+
// An app that came without Lamp's own files (a shared file carries none) is given this Lamp's, as its app would.
|
|
177
|
+
const missing = projectOf(folder).missing ?? [];
|
|
178
|
+
if (missing.length) writeMacStarter(folder, safeTitle(manifestOf(folder)?.name ?? basename(folder)), { only: missing });
|
|
179
|
+
const state = stateOf(folder), logFile = join(state, "log.txt"), runtime = join(folder, ".lamp", "runtime");
|
|
180
|
+
mkdirSync(state, { recursive: true });
|
|
181
|
+
// The app keeps its log and pictures here, as it does under Lamp's app, once the folder's .lamp is there.
|
|
182
|
+
mkdirSync(runtime, { recursive: true });
|
|
183
|
+
writeFileSync(logFile, "");
|
|
184
|
+
const print = (text) => { if (!quiet) console.log(`${new Date().toLocaleTimeString([], { hour12: false })} ${text}`); };
|
|
185
|
+
const slug = basename(folder), commandToken = randomBytes(24).toString("hex"), run = randomBytes(6).toString("hex");
|
|
186
|
+
// What only lamp and the app have, the same from one run to the next.
|
|
187
|
+
const tokenFile = join(state, "app-token");
|
|
188
|
+
if (!existsSync(tokenFile)) writeFileSync(tokenFile, randomBytes(24).toString("hex"), { mode: 0o600 });
|
|
189
|
+
const appToken = readFileSync(tokenFile, "utf8").trim();
|
|
190
|
+
const note = `dev.lamp.cli.${createHash("sha256").update(folder).digest("hex").slice(0, 8)}.`;
|
|
191
|
+
// The build's fence, kept with lamp's own state, which the fence closes: the build can't change it.
|
|
192
|
+
const fence = join(state, "sandbox");
|
|
193
|
+
mkdirSync(join(fence, "bin"), { recursive: true });
|
|
194
|
+
writeFileSync(join(fence, "bin", "swift"), SWIFT_SHIM);
|
|
195
|
+
chmodSync(join(fence, "bin", "swift"), 0o755);
|
|
196
|
+
writeFileSync(join(fence, "build.sb"), buildProfile(folder, join(fence, "bin")), { mode: 0o600 });
|
|
197
|
+
|
|
198
|
+
// MARK: The files and their changes
|
|
199
|
+
|
|
200
|
+
// What the build reads is a change: the code, the package and the app's own files. The app's notes and what Lamp
|
|
201
|
+
// knows of it beside them (APP.md, icon.json…) aren't built, so saving one builds nothing.
|
|
202
|
+
const ignored = (path) => path.split("/").some((part) => part.startsWith(".") || part === "build" || part === "node_modules") || path.endsWith("~")
|
|
203
|
+
|| (!path.includes("/") && /\.(md|json|txt)$/i.test(path));
|
|
204
|
+
const filesUnder = (dir = folder, prefix = "", found = []) => {
|
|
205
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
206
|
+
const path = prefix + entry.name;
|
|
207
|
+
if (ignored(path)) continue;
|
|
208
|
+
if (entry.isDirectory()) filesUnder(join(dir, entry.name), path + "/", found);
|
|
209
|
+
else if (entry.isFile()) found.push(path);
|
|
210
|
+
}
|
|
211
|
+
return found;
|
|
212
|
+
};
|
|
213
|
+
const stamp = (path) => { try { const s = statSync(join(folder, path)); return s.isFile() ? `${s.size}:${s.mtimeMs}` : null; } catch { return null; } };
|
|
214
|
+
const known = new Map(filesUnder().map((path) => [path, stamp(path)]));
|
|
215
|
+
|
|
216
|
+
let gen = 0, lastLookGen = 0;
|
|
217
|
+
const changes = []; // { gen, files, outcome, means, errors }
|
|
218
|
+
const log = [];
|
|
219
|
+
let logCount = 0, lastLookLine = 0;
|
|
220
|
+
function logged(text, level = "log") {
|
|
221
|
+
for (const line of String(text).split("\n")) {
|
|
222
|
+
if (!line.trim()) continue;
|
|
223
|
+
log.push({ n: ++logCount, level, text: line });
|
|
224
|
+
try { appendFileSync(logFile, `${level !== "log" ? level + ": " : ""}${line}\n`); } catch {}
|
|
225
|
+
}
|
|
226
|
+
if (log.length > 800) log.splice(0, log.length - 800);
|
|
227
|
+
}
|
|
228
|
+
const isError = (line) => line.level === "error" || /(^|\s)(Fatal error|fatal error|error):/.test(line.text);
|
|
229
|
+
// What the app prints goes to its own log in the folder (`LampRuntime`), a new one each launch: read on from where
|
|
230
|
+
// it was last read, from the start when the file is a new one.
|
|
231
|
+
const appLog = join(runtime, "log.txt");
|
|
232
|
+
// What an earlier run left there isn't this one's.
|
|
233
|
+
let appLogAt = (() => { try { return statSync(appLog).size; } catch { return 0; } })();
|
|
234
|
+
let appLogFile = (() => { try { return statSync(appLog).ino; } catch { return 0; } })();
|
|
235
|
+
function readAppLog() {
|
|
236
|
+
let size = 0;
|
|
237
|
+
try { const info = statSync(appLog); size = info.size; if (info.ino !== appLogFile) { appLogFile = info.ino; appLogAt = 0; } } catch { return; }
|
|
238
|
+
if (size < appLogAt) appLogAt = 0;
|
|
239
|
+
if (size === appLogAt) return;
|
|
240
|
+
const bytes = Buffer.alloc(Math.min(size - appLogAt, 400_000));
|
|
241
|
+
try { const file = openSync(appLog, "r"); readSync(file, bytes, 0, bytes.length, size - bytes.length); closeSync(file); } catch { return; }
|
|
242
|
+
appLogAt = size;
|
|
243
|
+
logged(bytes.toString("utf8"));
|
|
244
|
+
}
|
|
245
|
+
const appLogTimer = setInterval(readAppLog, 1000);
|
|
246
|
+
appLogTimer.unref();
|
|
247
|
+
|
|
248
|
+
// MARK: Building and running
|
|
249
|
+
|
|
250
|
+
let app = null; // build/<Name>.app once it's built
|
|
251
|
+
let running = null; // lamp's own copy of it, which is the one that runs
|
|
252
|
+
let shown = slug; // its name as it shows it
|
|
253
|
+
let child = null, launched = 0, quitting = false, exited = null;
|
|
254
|
+
let building = null; // the build under way, a promise
|
|
255
|
+
let pendingFiles = new Set(), timer = null;
|
|
256
|
+
|
|
257
|
+
/** Runs the app's own build script. What the compiler said, when it didn't build. */
|
|
258
|
+
function runScript(env) {
|
|
259
|
+
return new Promise((done) => {
|
|
260
|
+
const began = Date.now();
|
|
261
|
+
let said = "";
|
|
262
|
+
const script = spawn("/usr/bin/sandbox-exec", ["-f", join(fence, "build.sb"), "/bin/zsh", "build.sh"],
|
|
263
|
+
{ cwd: folder, env: { ...process.env, ...env, PATH: `${join(fence, "bin")}:${process.env.PATH}` }, stdio: ["ignore", "pipe", "pipe"] });
|
|
264
|
+
script.stdout.on("data", (chunk) => { said += chunk; });
|
|
265
|
+
script.stderr.on("data", (chunk) => { said += chunk; });
|
|
266
|
+
script.on("error", (error) => done({ ok: false, seconds: 0, errors: [error.message] }));
|
|
267
|
+
script.on("exit", (code) => {
|
|
268
|
+
const seconds = Math.round((Date.now() - began) / 100) / 10;
|
|
269
|
+
const made = existsSync(join(folder, "build")) ? readdirSync(join(folder, "build")).find((name) => name.endsWith(".app")) : null;
|
|
270
|
+
if (code === 0 && made) return done({ ok: true, seconds, app: join(folder, "build", made) });
|
|
271
|
+
// The compiler's own errors (a file, a line and what's wrong), each once, without its colors or the folder in
|
|
272
|
+
// front of every path.
|
|
273
|
+
const plain = said.replace(/\x1b\[[0-9;]*m/g, "").replace(/\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
|
|
274
|
+
const errors = [...new Set(plain.split("\n").filter((line) => /^\S.*:\d+:\d+: error: /.test(line)).map((line) => line.replaceAll(folder + "/", "").trim()))];
|
|
275
|
+
const other = plain.split("\n").filter((line) => /^error: /.test(line) && !/Command line:/.test(line)).map((line) => line.slice(0, 300));
|
|
276
|
+
done({ ok: false, seconds, errors: errors.length ? errors.slice(0, 20) : other.length ? other.slice(0, 8) : plain.trim().split("\n").slice(-8) });
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// The build Lamp's app makes while an app is being changed (LAMP_LIVE: a debug build of a copy of the sources in
|
|
282
|
+
// .lamp/live, which builds only what changed) takes a few seconds where the plain one takes most of a minute. The
|
|
283
|
+
// copy has lines of Lamp's own in it, so when it doesn't build, the plain build says what's wrong in the app's own
|
|
284
|
+
// code, and where; and an app whose copy doesn't build though its code does is built plainly from then on.
|
|
285
|
+
let quick = true;
|
|
286
|
+
async function build() {
|
|
287
|
+
if (quick) {
|
|
288
|
+
const live = await runScript({ LAMP_LIVE: "1" });
|
|
289
|
+
if (live.ok) return live;
|
|
290
|
+
}
|
|
291
|
+
const plain = await runScript({ LAMP_LIVE: "" });
|
|
292
|
+
if (plain.ok && quick) quick = false;
|
|
293
|
+
return plain;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Takes what the build made out of the folder, to where the build can't reach, and tells that copy where its Lamp
|
|
298
|
+
* is: here. So what only lamp and the app have is never in the folder, for a later build to read, and the app
|
|
299
|
+
* that runs is never one a build changed after it was made. The app's signature covers its Info.plist, so the copy
|
|
300
|
+
* is signed again.
|
|
301
|
+
*/
|
|
302
|
+
function stage(built) {
|
|
303
|
+
const bundle = join(state, "app", basename(built));
|
|
304
|
+
rmSync(join(state, "app"), { recursive: true, force: true });
|
|
305
|
+
mkdirSync(join(state, "app"), { recursive: true });
|
|
306
|
+
cpSync(built, bundle, { recursive: true, verbatimSymlinks: true });
|
|
307
|
+
running = bundle;
|
|
308
|
+
const plist = join(bundle, "Contents", "Info.plist");
|
|
309
|
+
const read = (key) => spawnSync("/usr/bin/plutil", ["-extract", key, "raw", plist], { encoding: "utf8" }).stdout.trim();
|
|
310
|
+
const values = {
|
|
311
|
+
slug, folder: slug, apps: dirname(folder), dataID: read("CFBundleIdentifier"), token: appToken,
|
|
312
|
+
// Where the app asks Lamp for what it keeps for it: lamp's own server for this folder, which answers its
|
|
313
|
+
// secrets and its Reload, and nothing else yet (its AI, sharing), which the app takes as Lamp saying no.
|
|
314
|
+
port: String(port), scheme: "lamp-cli", note, host: "dev.lamp.cli", macHost: "127.0.0.1", widgetPort: "0", widgetToken: "",
|
|
315
|
+
};
|
|
316
|
+
spawnSync("/usr/bin/plutil", ["-replace", "LampInstall", "-json", JSON.stringify(values), plist], { stdio: "ignore" });
|
|
317
|
+
spawnSync("/usr/bin/codesign", ["--force", "-s", "-", bundle], { stdio: "ignore" });
|
|
318
|
+
shown = read("CFBundleName") || shown;
|
|
319
|
+
return read("CFBundleExecutable");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function launch() {
|
|
323
|
+
const exe = stage(app);
|
|
324
|
+
readAppLog();
|
|
325
|
+
exited = null;
|
|
326
|
+
quitting = false;
|
|
327
|
+
launched = Date.now();
|
|
328
|
+
// Kept awake while nobody looks at it (a default macOS reads from an app's arguments): an app the system has
|
|
329
|
+
// put to sleep behind other windows, or with the display off, answers lamp's asking for a picture late or not at all.
|
|
330
|
+
child = spawn(join(running, "Contents", "MacOS", exe), ["-NSAppSleepDisabled", "YES"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
331
|
+
const mine = child;
|
|
332
|
+
child.stdout.on("data", (chunk) => logged(chunk));
|
|
333
|
+
child.stderr.on("data", (chunk) => logged(chunk));
|
|
334
|
+
child.on("exit", (code, signal) => {
|
|
335
|
+
if (child !== mine) return;
|
|
336
|
+
child = null;
|
|
337
|
+
if (quitting) return;
|
|
338
|
+
// It went without being asked: it crashed, or the person quit it.
|
|
339
|
+
exited = { code, signal, at: Date.now() };
|
|
340
|
+
if (signal || code) { logged(`The app quit unexpectedly (${signal ?? `exit ${code}`}).`, "error"); print(` ! the app quit unexpectedly (${signal ?? `exit ${code}`})`); }
|
|
341
|
+
else print("the app was quit");
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function quit() {
|
|
346
|
+
if (!child) return;
|
|
347
|
+
quitting = true;
|
|
348
|
+
const going = child;
|
|
349
|
+
going.kill("SIGTERM");
|
|
350
|
+
for (let tries = 0; tries < 30 && child === going; tries++) await sleep(100);
|
|
351
|
+
if (child === going) { going.kill("SIGKILL"); await sleep(200); child = null; }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Builds what the folder holds now and opens it in place of what's running. One at a time. */
|
|
355
|
+
function rebuild(files) {
|
|
356
|
+
const change = files ? { gen: ++gen, files: [...files].sort(), outcome: "building", means: "being built", errors: [] } : null;
|
|
357
|
+
if (change) { changes.push(change); if (changes.length > 40) changes.shift(); }
|
|
358
|
+
const named = change ? (change.files.length > 4 ? `${change.files.slice(0, 3).join(", ")} and ${change.files.length - 3} more` : change.files.join(", ")) : "the app";
|
|
359
|
+
building = (building ?? Promise.resolve()).then(async () => {
|
|
360
|
+
const built = await build();
|
|
361
|
+
if (!built.ok) {
|
|
362
|
+
if (change) Object.assign(change, { outcome: "not built", means: "it didn't build, so the app is as it was", errors: built.errors });
|
|
363
|
+
for (const error of built.errors) logged(error, "error");
|
|
364
|
+
print(`${named} → didn't build (${built.seconds} s)`);
|
|
365
|
+
for (const error of built.errors.slice(0, 5)) print(` ! ${error.slice(0, 300)}`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
app = built.app;
|
|
369
|
+
await quit();
|
|
370
|
+
launch();
|
|
371
|
+
if (change) Object.assign(change, { outcome: "reopened", means: `built in ${built.seconds} s and opened again` });
|
|
372
|
+
print(`${named} → built in ${built.seconds} s and opened${change ? " again" : ""}`);
|
|
373
|
+
}).catch((error) => { logged(String(error?.message ?? error), "error"); });
|
|
374
|
+
const mine = building;
|
|
375
|
+
mine.finally(() => { if (building === mine) building = null; });
|
|
376
|
+
return mine;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function check(paths) {
|
|
380
|
+
const changed = [];
|
|
381
|
+
for (const path of paths) {
|
|
382
|
+
const now = stamp(path), before = known.get(path);
|
|
383
|
+
if (now === before || (now === null && before === undefined)) continue;
|
|
384
|
+
if (now === null) known.delete(path); else known.set(path, now);
|
|
385
|
+
changed.push(now === null ? `${path} (removed)` : path);
|
|
386
|
+
}
|
|
387
|
+
if (changed.length) rebuild(changed);
|
|
388
|
+
}
|
|
389
|
+
function noticed(path) {
|
|
390
|
+
if (!path) return;
|
|
391
|
+
path = path.split(sep).join("/");
|
|
392
|
+
if (ignored(path)) return;
|
|
393
|
+
pendingFiles.add(path);
|
|
394
|
+
clearTimeout(timer);
|
|
395
|
+
// Longer than for a page: a change here costs a build, and an edit of several files is one change.
|
|
396
|
+
timer = setTimeout(() => { timer = null; const paths = pendingFiles; pendingFiles = new Set(); check(paths); }, 500);
|
|
397
|
+
}
|
|
398
|
+
function checkAll() {
|
|
399
|
+
clearTimeout(timer);
|
|
400
|
+
timer = null;
|
|
401
|
+
pendingFiles = new Set();
|
|
402
|
+
check(new Set([...known.keys(), ...filesUnder()]));
|
|
403
|
+
}
|
|
404
|
+
const watcher = watch(folder, { recursive: true }, (_event, name) => noticed(name));
|
|
405
|
+
watcher.on("error", () => {});
|
|
406
|
+
|
|
407
|
+
// MARK: Looking
|
|
408
|
+
|
|
409
|
+
let pictures = 0, lastUsed = Date.now();
|
|
410
|
+
for (const name of readdirSync(state)) if (/^look(-\d+)*\.png$/.test(name)) rmSync(join(state, name), { force: true });
|
|
411
|
+
|
|
412
|
+
/** How the changes since the last look went, pictures of the app's windows, its screen as text, and its log. */
|
|
413
|
+
async function look({ wait = 120, reopen = false } = {}) {
|
|
414
|
+
lastUsed = Date.now();
|
|
415
|
+
checkAll();
|
|
416
|
+
const until = Date.now() + Math.max(0, wait) * 1000;
|
|
417
|
+
while ((building || timer) && Date.now() < until) await sleep(100);
|
|
418
|
+
// Opened again from its start, as someone opening it finds it: what it saved is still there.
|
|
419
|
+
if (reopen && app && !building) { await quit(); launch(); }
|
|
420
|
+
const unfinished = building || timer ? "the app was still being built" : null;
|
|
421
|
+
let picture = null, more = [], screen = null, problem = null, remark = null;
|
|
422
|
+
if (child) {
|
|
423
|
+
// A moment for its first window, then the app pictures its own windows and says what's on them.
|
|
424
|
+
if (Date.now() - launched < 2500) await sleep(2500 - (Date.now() - launched));
|
|
425
|
+
const shot = join(runtime, "screenshot.png"), text = join(runtime, "screen.txt");
|
|
426
|
+
const before = (() => { try { return statSync(shot).mtimeMs; } catch { return 0; } })();
|
|
427
|
+
post(`${note}snapshot.${slug}`);
|
|
428
|
+
// The first picture after a build takes longer: the system checks the app's new signature before it answers.
|
|
429
|
+
for (let tries = 0, most = Date.now() - launched < 20_000 ? 150 : 60; tries < most; tries++) {
|
|
430
|
+
try { if (statSync(shot).mtimeMs > before) break; } catch {}
|
|
431
|
+
await sleep(100);
|
|
432
|
+
}
|
|
433
|
+
const taken = (() => { try { return statSync(shot).mtimeMs; } catch { return 0; } })();
|
|
434
|
+
if (taken > before || (taken && taken >= launched)) {
|
|
435
|
+
await sleep(150);
|
|
436
|
+
const n = ++pictures;
|
|
437
|
+
picture = join(state, `look-${n}.png`);
|
|
438
|
+
copyFileSync(shot, picture);
|
|
439
|
+
for (const index of [2, 3]) {
|
|
440
|
+
const other = join(runtime, `screenshot-${index}.png`);
|
|
441
|
+
try { if (statSync(other).mtimeMs >= taken - 2000) { const to = join(state, `look-${n}-${index}.png`); copyFileSync(other, to); more.push(to); } } catch {}
|
|
442
|
+
}
|
|
443
|
+
rmSync(join(state, `look-${n - 40}.png`), { force: true });
|
|
444
|
+
try { if (statSync(text).mtimeMs >= taken - 2000) screen = readFileSync(text, "utf8").trim() || null; } catch {}
|
|
445
|
+
// The app reads its screen through accessibility, on itself, which answers only once the app has been in
|
|
446
|
+
// front with the screen unlocked.
|
|
447
|
+
if (!screen) remark = "The app gave no screen as text (it can read its own screen only once it has been in front, with the Mac unlocked): aim by the picture, whose points are the points to click.";
|
|
448
|
+
if (taken <= before) problem = "The app took no new picture just now (its windows can't be pictured while the screen is locked or the display is asleep): this is its last one.";
|
|
449
|
+
} else {
|
|
450
|
+
problem = "The app took no picture of its windows: they can't be pictured while the screen is locked, the display is asleep, or the app is hidden.";
|
|
451
|
+
}
|
|
452
|
+
} else if (!app) {
|
|
453
|
+
problem = "The app isn't built yet, so there's nothing to look at.";
|
|
454
|
+
} else {
|
|
455
|
+
problem = exited && (exited.signal || exited.code) ? "The app isn't running: it quit unexpectedly. Its log says why." : "The app isn't running: it was quit. A change, or lamp reload, opens it again.";
|
|
456
|
+
}
|
|
457
|
+
readAppLog();
|
|
458
|
+
const since = changes.filter((change) => change.gen > lastLookGen)
|
|
459
|
+
.map((change) => ({ files: change.files.length > 4 ? `${change.files.slice(0, 3).join(", ")} and ${change.files.length - 3} more` : change.files.join(", "), outcome: change.outcome, means: change.means, why: "" }));
|
|
460
|
+
const fresh = log.filter((line) => line.n > lastLookLine);
|
|
461
|
+
const errors = [...new Set(fresh.filter(isError).map((line) => line.text))];
|
|
462
|
+
lastLookGen = gen;
|
|
463
|
+
lastLookLine = logCount;
|
|
464
|
+
return {
|
|
465
|
+
where: "mac", opened: false, playing: null, address: app, picture, pictures: more, title: shown, width: null, height: null, screen,
|
|
466
|
+
unfinished, changes: since, errors: errors.slice(-20), note: remark,
|
|
467
|
+
log: fresh.filter((line) => !isError(line)).slice(-25).map((line) => line.text), logLines: fresh.length, logFile, problem,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Things done in the app as a person would, by the app's own driver (`LampDriver` in its support code), asked the
|
|
473
|
+
* way Lamp's app asks: signed with what only lamp and the app have, and answered in the app's own words.
|
|
474
|
+
*/
|
|
475
|
+
async function use(asked) {
|
|
476
|
+
lastUsed = Date.now();
|
|
477
|
+
const moves = Array.isArray(asked?.moves) ? asked.moves : [asked];
|
|
478
|
+
if (building) await building;
|
|
479
|
+
if (!child) throw new Problem("the app isn't running: lamp look says why");
|
|
480
|
+
// In front, as Lamp's app puts it: the person sees what's done, and what only answers a real click takes it.
|
|
481
|
+
activate();
|
|
482
|
+
const actions = macActions(moves);
|
|
483
|
+
const id = randomBytes(8).toString("hex"), json = JSON.stringify(actions), answerFile = join(runtime, "use.json");
|
|
484
|
+
rmSync(answerFile, { force: true });
|
|
485
|
+
post(`${note}useApp.${slug}`, { actions: json, id, lampUseAppSignature: createHmac("sha256", appToken).update(`${id}\n${json}`).digest("hex") });
|
|
486
|
+
const until = Date.now() + 20_000 + actions.reduce((total, action) => total + (action.amount && action.action !== "scroll" ? action.amount * 1000 : 0), 0);
|
|
487
|
+
let answer = null;
|
|
488
|
+
while (Date.now() < until && child) {
|
|
489
|
+
try { const read = JSON.parse(readFileSync(answerFile, "utf8")); if (read.id === id) { answer = read; break; } } catch {}
|
|
490
|
+
await sleep(100);
|
|
491
|
+
}
|
|
492
|
+
const did = answer ? (answer.report || "Done.") + (answer.stopped ? "\nThe person took the app back: it's theirs until they ask again." : "")
|
|
493
|
+
: child ? "The app didn't answer: nothing may have been done." : "The app quit while it was being used.";
|
|
494
|
+
return { did, ...(await look({ wait: 30 })) };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function activate() {
|
|
498
|
+
if (!child) return;
|
|
499
|
+
spawnSync("/usr/bin/osascript", ["-l", "JavaScript", "-e", `ObjC.import("AppKit"); $.NSRunningApplication.runningApplicationWithProcessIdentifier(${child.pid}).activateWithOptions(2)`], { stdio: "ignore", timeout: 10_000 });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// MARK: What the app asks of Lamp
|
|
503
|
+
|
|
504
|
+
// The app's secrets (a service's sign-in, a key: `LampSecrets` in its support code), kept for it here as Lamp's app
|
|
505
|
+
// keeps them, since macOS ties a Keychain item of the app's own to one build of it. With lamp's state for the
|
|
506
|
+
// folder, readable only by the person, never in the folder.
|
|
507
|
+
const secretsFile = join(state, "secrets.json");
|
|
508
|
+
const secrets = () => { try { return JSON.parse(readFileSync(secretsFile, "utf8")); } catch { return {}; } };
|
|
509
|
+
|
|
510
|
+
/** The answer to one thing the app asked: a status and what's said. */
|
|
511
|
+
async function asksOfLamp(path, asked) {
|
|
512
|
+
if (path === "/app/secrets") {
|
|
513
|
+
const name = typeof asked.name === "string" ? asked.name : "";
|
|
514
|
+
if (!name || name.length > 200) return [400, { error: "a secret has a name" }];
|
|
515
|
+
const kept = secrets();
|
|
516
|
+
if (asked.op === "read") return [200, { ok: true, ...(typeof kept[name] === "string" ? { value: kept[name] } : {}) }];
|
|
517
|
+
if (asked.op === "save" && typeof asked.value === "string" && asked.value.length < 200_000) kept[name] = asked.value;
|
|
518
|
+
else if (asked.op === "delete") delete kept[name];
|
|
519
|
+
else return [400, { error: "read, save or delete" }];
|
|
520
|
+
writeFileSync(secretsFile, JSON.stringify(kept), { mode: 0o600 });
|
|
521
|
+
return [200, { ok: true }];
|
|
522
|
+
}
|
|
523
|
+
// The app menu's Reload asks whether Lamp has a change of the app's waiting to go in: here a change goes in as
|
|
524
|
+
// it's saved, so none is, and the app opens itself again.
|
|
525
|
+
if (path === "/app/reload") return [200, { waiting: false }];
|
|
526
|
+
// Its AI, sharing and the rest are Lamp's app's still.
|
|
527
|
+
logged(`The app asked Lamp for ${path}, which lamp doesn't answer yet.`);
|
|
528
|
+
return [404, { error: "Lamp's command line doesn't answer this yet." }];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// MARK: lamp's own commands
|
|
532
|
+
|
|
533
|
+
let turns = Promise.resolve();
|
|
534
|
+
const inTurn = (work) => { const mine = turns.then(work, work); turns = mine.catch(() => {}); return mine; };
|
|
535
|
+
|
|
536
|
+
async function body(request) {
|
|
537
|
+
const chunks = [];
|
|
538
|
+
for await (const chunk of request) chunks.push(chunk);
|
|
539
|
+
try { return JSON.parse(Buffer.concat(chunks).toString("utf8")); } catch { return null; }
|
|
540
|
+
}
|
|
541
|
+
const server = createServer((request, response) => {
|
|
542
|
+
answer(request, response).catch(() => { try { if (!response.headersSent) response.writeHead(400); response.end(); } catch {} });
|
|
543
|
+
});
|
|
544
|
+
async function answer(request, response) {
|
|
545
|
+
const url = new URL(request.url, "http://localhost");
|
|
546
|
+
const json = (status, value) => { response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); response.end(JSON.stringify(value)); };
|
|
547
|
+
const host = String(request.headers.host || "").replace(/:\d+$/, "").toLowerCase();
|
|
548
|
+
if (!["localhost", "127.0.0.1"].includes(host) || request.headers.origin) return json(403, { problem: "not lamp's own command" });
|
|
549
|
+
// What the app itself asks of its Lamp (`LampInstall.port`), shown the token only the two have.
|
|
550
|
+
if (url.pathname.startsWith("/app/") || url.pathname.startsWith("/ai/")) {
|
|
551
|
+
if (request.headers.authorization !== `Bearer ${appToken}` || request.method !== "POST") return json(403, { error: "not this app" });
|
|
552
|
+
return json(...(await asksOfLamp(url.pathname, (await body(request)) ?? {})));
|
|
553
|
+
}
|
|
554
|
+
if (!url.pathname.startsWith("/__lamp/cli/") || request.headers["x-lamp-token"] !== commandToken) return json(403, { problem: "that isn't lamp's own command" });
|
|
555
|
+
const what = url.pathname.slice("/__lamp/cli/".length);
|
|
556
|
+
try {
|
|
557
|
+
if (what === "hello") return json(200, { folder, url: app ?? `${slug} (being built)`, pid: process.pid, run, own: idle > 0, kind: "mac" });
|
|
558
|
+
if (request.method !== "POST") return json(405, { problem: "not a thing to ask that way" });
|
|
559
|
+
const asked = (await body(request)) ?? {};
|
|
560
|
+
if (what === "look") return json(200, await inTurn(() => look(asked)));
|
|
561
|
+
if (what === "open") {
|
|
562
|
+
// Opened again when it was quit, and brought to the front.
|
|
563
|
+
if (!child && app && !building) launch();
|
|
564
|
+
activate();
|
|
565
|
+
return json(200, { opened: "app", url: shown, running: !!child, building: !!building });
|
|
566
|
+
}
|
|
567
|
+
if (what === "reload") { if (app) { await quit(); launch(); } return json(200, { pages: app ? 1 : 0 }); }
|
|
568
|
+
if (what === "stop") { json(200, { stopped: true }); setTimeout(() => close().then(() => process.exit(0)), 50); return; }
|
|
569
|
+
if (what === "use") return json(200, await inTurn(() => use(asked)));
|
|
570
|
+
if (what === "card") return json(400, { problem: "sharing a Mac app is Lamp's app's still" });
|
|
571
|
+
return json(404, { problem: `lamp dev doesn't know "${what}"` });
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return json(error instanceof Problem ? 400 : 500, { problem: error.message });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const portFile = join(state, "port");
|
|
578
|
+
const remembered = Number((() => { try { return readFileSync(portFile, "utf8"); } catch { return ""; } })()) || 0;
|
|
579
|
+
const first = firstPortOf(folder);
|
|
580
|
+
let port = 0;
|
|
581
|
+
for (const candidate of asked ? [asked] : [...new Set([remembered || first, ...Array.from({ length: 30 }, (_, i) => first + i)])]) {
|
|
582
|
+
try {
|
|
583
|
+
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(candidate, "127.0.0.1", () => { server.off("error", reject); resolve(); }); });
|
|
584
|
+
port = candidate;
|
|
585
|
+
break;
|
|
586
|
+
} catch (error) { if (error.code !== "EADDRINUSE") throw new Problem(error.message); }
|
|
587
|
+
}
|
|
588
|
+
if (!port) throw new Problem("no port is free for lamp's own commands");
|
|
589
|
+
writeFileSync(portFile, String(port));
|
|
590
|
+
writeFileSync(serverFileOf(folder), JSON.stringify({ port, pid: process.pid, token: commandToken, folder }), { mode: 0o600 });
|
|
591
|
+
|
|
592
|
+
let closed = false;
|
|
593
|
+
async function close() {
|
|
594
|
+
if (closed) return;
|
|
595
|
+
closed = true;
|
|
596
|
+
clearInterval(idleTimer);
|
|
597
|
+
clearInterval(appLogTimer);
|
|
598
|
+
clearTimeout(timer);
|
|
599
|
+
watcher.close();
|
|
600
|
+
await quit();
|
|
601
|
+
try { if (JSON.parse(readFileSync(serverFileOf(folder), "utf8")).pid === process.pid) rmSync(serverFileOf(folder)); } catch {}
|
|
602
|
+
server.closeAllConnections?.();
|
|
603
|
+
await new Promise((done) => server.close(done));
|
|
604
|
+
}
|
|
605
|
+
process.on("exit", () => { try { child?.kill("SIGTERM"); } catch {} });
|
|
606
|
+
// A server `lamp look` started for itself ends once nothing has used it for a while, never under an app that's open.
|
|
607
|
+
const idleTimer = setInterval(() => {
|
|
608
|
+
if (!idle || Date.now() - lastUsed < idle * 60_000 || building || child) return;
|
|
609
|
+
close().then(() => process.exit(0));
|
|
610
|
+
}, 30_000);
|
|
611
|
+
idleTimer.unref();
|
|
612
|
+
|
|
613
|
+
if (!quiet) {
|
|
614
|
+
console.log(`lamp dev a Mac app in ${folder}`);
|
|
615
|
+
console.log(` Look lamp look how a change went, pictures of its windows, its screen as text and its log`);
|
|
616
|
+
console.log(` Log ${logFile}`);
|
|
617
|
+
console.log(" Each save builds the app again and opens it again.\n");
|
|
618
|
+
}
|
|
619
|
+
rebuild(null);
|
|
620
|
+
return { url: null, port, token: commandToken, folder, state, logFile, look: (options) => inTurn(() => look(options)), close };
|
|
621
|
+
}
|