on master help sign in
Merge remote-tracking branch 'origin/master' into db
ee81335Ryan Trinkle
README.md+11
@@ -7,6 +7,8 @@ Obelisk provides an easy way to develop and deploy your [Reflex](https://github.
7- [Deploying](#deploying)
7- [Deploying](#deploying)
8 - [Locally](#locally)
8 - [Locally](#locally)
9 - [EC2](#ec2)
9 - [EC2](#ec2)
10+ - [From macOS](#from-macos)
11+ - [Deploying an updated version](#deploying-an-updated-version)
10- [Mobile](#mobile)
12- [Mobile](#mobile)
11 - [iOS](#ios)
13 - [iOS](#ios)
12 - [Android](#android)
14 - [Android](#android)
@@ -156,6 +158,15 @@ ob deploy push
156
158
157At this point you are done. Your app will be accessible at `${ROUTE}`.
159At this point you are done. Your app will be accessible at `${ROUTE}`.
158
160
161+### From macOS
162+
163+Deploying from macOS requires some extra setup:
164+
165+- [Install nix-darwin](https://github.com/LnL7/nix-darwin)
166+- [Install docker](https://docs.docker.com/)
167+
168+Running `ob deploy push` will give you additional setup instructions.
169+
159### Deploying an updated version
170### Deploying an updated version
160
171
161If you'd like to deploy an updated version (with new commits) of your Obelisk app: simply go to the configuration directory, update the source thunk and push:
172If you'd like to deploy an updated version (with new commits) of your Obelisk app: simply go to the configuration directory, update the source thunk and push:
default.nix+168−159
@@ -1,20 +1,13 @@
1{ system ? builtins.currentSystem
1{ system ? builtins.currentSystem
2, profiling ? false
2, profiling ? false
3, iosSdkVersion ? "10.2"
3, iosSdkVersion ? "10.2"
4+, __useLegacyCompilers ? false
4}:
5}:
5let
6let
6− getReflexPlatform = sys: import ./dep/reflex-platform { inherit iosSdkVersion; system = sys; };
7+ cleanSource = builtins.filterSource (name: _: let baseName = builtins.baseNameOf name; in !(
7− reflex-platform = getReflexPlatform system;
8+ builtins.match "^\\.ghc\\.environment.*" baseName != null ||
8− inherit (reflex-platform) hackGet nixpkgs;
9+ baseName == "cabal.project.local"
9− pkgs = nixpkgs;
10+ ));
10−in with pkgs.haskell.lib; with pkgs.lib;
11−let
12− # TODO: Remove this after updating nixpkgs: https://github.com/NixOS/nixpkgs/issues/37750
13− justStaticExecutables' = drv: let
14− drv' = justStaticExecutables drv;
15− in if pkgs.stdenv.isDarwin
16− then removeConfigureFlag drv' "--ghc-option=-optl=-dead_strip"
17− else drv';
18
11
19 commandRuntimeDeps = pkgs: with pkgs; [
12 commandRuntimeDeps = pkgs: with pkgs; [
20 coreutils
13 coreutils
@@ -24,39 +17,6 @@ let
24 openssh
17 openssh
25 ];
18 ];
26
19
27− #TODO: Upstream
28− # Modify a Haskell package to add completion scripts for the given
29− # executable produced by it. These completion scripts will be picked up
30− # automatically if the resulting derivation is installed, e.g. by
31− # `nix-env -i`.
32− addOptparseApplicativeCompletionScripts = exeName: pkg: overrideCabal pkg (drv: {
33− postInstall = (drv.postInstall or "") + ''
34− BASH_COMP_DIR="$out/share/bash-completion/completions"
35− mkdir -p "$BASH_COMP_DIR"
36− "$out/bin/${exeName}" --bash-completion-script "$out/bin/${exeName}" >"$BASH_COMP_DIR/ob"
37−
38− ZSH_COMP_DIR="$out/share/zsh/vendor-completions"
39− mkdir -p "$ZSH_COMP_DIR"
40− "$out/bin/${exeName}" --zsh-completion-script "$out/bin/${exeName}" >"$ZSH_COMP_DIR/_ob"
41−
42− FISH_COMP_DIR="$out/share/fish/vendor_completions.d"
43− mkdir -p "$FISH_COMP_DIR"
44− "$out/bin/${exeName}" --fish-completion-script "$out/bin/${exeName}" >"$FISH_COMP_DIR/ob.fish"
45− '';
46− });
47−
48− # The haskell environment used to build Obelisk itself, e.g. the 'ob' command
49− ghcObelisk = reflex-platform.ghc.override {
50− overrides = composeExtensions defaultHaskellOverrides (self: super: {
51− mkDerivation = args: super.mkDerivation (args // {
52− enableLibraryProfiling = profiling;
53− });
54−
55− # Dynamic linking with split objects dramatically increases startup time (about 0.5 seconds on a decent machine with SSD)
56− obelisk-command = addOptparseApplicativeCompletionScripts "ob" (justStaticExecutables' super.obelisk-command);
57− });
58− };
59−
60 beam-src = pkgs.fetchFromGitHub {
20 beam-src = pkgs.fetchFromGitHub {
61 owner = "obsidiansystems";
21 owner = "obsidiansystems";
62 repo = "beam";
22 repo = "beam";
@@ -71,97 +31,150 @@ let
71 sha256 = "0257p0qd8xx900ngghkjbmjnvn7pjv05g0jm5kkrm4p6alrlhfyl";
31 sha256 = "0257p0qd8xx900ngghkjbmjnvn7pjv05g0jm5kkrm4p6alrlhfyl";
72 };
32 };
73
33
74− fixUpstreamPkgs = self: super: {
34+ getReflexPlatform = getReflexPlatform' __useLegacyCompilers;
75− algebraic-graphs = pkgs.haskell.lib.doJailbreak
35+ getReflexPlatform' = __useLegacyCompilers: sys: import ./dep/reflex-platform {
76− (self.callCabal2nix "algebraic-graphs" (pkgs.fetchFromGitHub {
36+ inherit iosSdkVersion __useLegacyCompilers;
77− owner = "snowleopard";
37+ system = sys;
78− repo = "alga";
38+ enableLibraryProfiling = profiling;
79− rev = "480a73137e9b38ad3f1bc2c628847953d2fb3e25";
80− sha256 = "0dpwi5ffs88brl3lz51bwb004c6zm8ds8pkw1vzsg2a6aaiyhlzl";
81− }) {});
82−
83− # Need deriveSomeUniverse
84− # PR: https://github.com/dmwit/universe/pull/32
85− universe-template = self.callCabal2nix "universe-template" (pkgs.fetchFromGitHub {
86− owner = "dmwit";
87− repo = "universe";
88− rev = "75764f400eca25c45bb1887ac3e1dcc2d99c92b1";
89− sha256 = "1kf3f77jw5shwy5s3iq5h7lqzr775c44ia883ycm0m441c311pan";
90− } + /template) {};
91−
92− # Need ShowTag, EqTag, and OrdTag instances
93− dependent-sum-template = self.callCabal2nix "dependent-sum-template" (pkgs.fetchFromGitHub {
94− owner = "mokus0";
95− repo = "dependent-sum-template";
96− rev = "dcb92a98d35d79f712b9a398c8c356acc979ecff";
97− sha256 = "1v337d810d88jzfriw07pr16d34nibm6q2zkw787lc3sfn07glp5";
98− }) {};
99−
100− beam-core = self.callCabal2nix "beam-core" (beam-src + /beam-core) {};
101− beam-postgres = dontCheck (self.callCabal2nix "beam-postgres" (beam-src + /beam-postgres) {});
102− beam-migrate = self.callCabal2nix "beam-migrate" (beam-src + /beam-migrate) {};
103−
104− websockets = self.callCabal2nix "websockets" (pkgs.fetchFromGitHub {
105− owner = "obsidiansystems";
106− repo = "websockets";
107− rev = "b750edf10fc6f532f9fb36588904491af87606c7";
108− sha256 = "1wd0gkx8a4x6n5cdkra96zvpv8l49i0z90amvrng70ybnzbxpfi5";
109− }) {};
110−
111− gargoyle = doJailbreak (self.callCabal2nix "gargoyle" (gargoyle-src + /gargoyle) {});
112− gargoyle-postgresql-nix = pkgs.haskell.lib.addBuildTool
113− (self.callCabal2nix "gargoyle-postgresql-nix" (gargoyle-src + /gargoyle-postgresql-nix) {})
114− pkgs.postgresql; # `staticWhich` requires `psql` on PATH during build time
115− gargoyle-postgresql = addBuildDepend (doJailbreak (self.callCabal2nix "gargoyle-postgresql" (gargoyle-src + /gargoyle-postgresql) {})) pkgs.postgresql;
116− postgresql-libpq = enableCabalFlag (overrideCabal super.postgresql-libpq (drv: {
117− pkgconfigDepends = (drv.pkgconfigDepends or []) ++ [pkgs.postgresql]; #TODO: Obelisk should probably provide this version of postgresql
118− })) "use-pkg-config";
119− };
120
39
121− cleanSource = builtins.filterSource (name: _: let baseName = builtins.baseNameOf name; in !(
40+ nixpkgsOverlays = [
122− builtins.match "^\\.ghc\\.environment.*" baseName != null ||
41+ (self: super: {
123− baseName == "cabal.project.local"
42+ obeliskExecutableConfig = import ./lib/executable-config { nixpkgs = pkgs; filterGitSource = cleanSource; };
124− ));
43+ })
44+ ];
45+
46+ haskellOverlays = [
47+
48+ # Fix misc upstream packages
49+ (self: super: let
50+ pkgs = self.callPackage ({ pkgs }: pkgs) {};
51+ in {
52+ # Need 8.0.2 build support
53+ # PR: https://github.com/dmwit/universe/pull/33
54+ universe-template = self.callCabal2nix "universe-template" (pkgs.fetchFromGitHub {
55+ owner = "obsidiansystems";
56+ repo = "universe";
57+ rev = "6a71119bfa5db2b9990a2491c941469ff8ef5d13";
58+ sha256 = "0z8smyainnlzcglv3dlx6x1n9j6d2jv48aa8f2421iayfkxg3js5";
59+ } + /template) {};
125
60
126− executableConfig = import ./lib/executable-config { nixpkgs = pkgs; filterGitSource = cleanSource; };
61+ algebraic-graphs = pkgs.haskell.lib.doJailbreak
127−
62+ (self.callCabal2nix "algebraic-graphs" (pkgs.fetchFromGitHub {
128− addLibs = self: super: {
63+ owner = "snowleopard";
129− obelisk-asset-manifest = self.callCabal2nix "obelisk-asset-manifest" (hackGet ./lib/asset + "/manifest") {};
64+ repo = "alga";
130− obelisk-asset-serve-snap = self.callCabal2nix "obelisk-asset-serve-snap" (hackGet ./lib/asset + "/serve-snap") {};
65+ rev = "480a73137e9b38ad3f1bc2c628847953d2fb3e25";
131− obelisk-backend = self.callCabal2nix "obelisk-backend" (cleanSource ./lib/backend) {};
66+ sha256 = "0dpwi5ffs88brl3lz51bwb004c6zm8ds8pkw1vzsg2a6aaiyhlzl";
132− obelisk-cliapp = self.callCabal2nix "obelisk-cliapp" (cleanSource ./lib/cliapp) {};
67+ }) {});
133− obelisk-command = (self.callCabal2nix "obelisk-command" (cleanSource ./lib/command) {});
68+
69+ # Need ShowTag, EqTag, and OrdTag instances
70+ dependent-sum-template = self.callCabal2nix "dependent-sum-template" (pkgs.fetchFromGitHub {
71+ owner = "mokus0";
72+ repo = "dependent-sum-template";
73+ rev = "dcb92a98d35d79f712b9a398c8c356acc979ecff";
74+ sha256 = "1v337d810d88jzfriw07pr16d34nibm6q2zkw787lc3sfn07glp5";
75+ }) {};
76+
77+ beam-core = self.callCabal2nix "beam-core" (beam-src + /beam-core) {};
78+ beam-postgres = dontCheck (self.callCabal2nix "beam-postgres" (beam-src + /beam-postgres) {});
79+ beam-migrate = self.callCabal2nix "beam-migrate" (beam-src + /beam-migrate) {};
80+
81+ websockets = self.callCabal2nix "websockets" (pkgs.fetchFromGitHub {
82+ owner = "obsidiansystems";
83+ repo = "websockets";
84+ rev = "b750edf10fc6f532f9fb36588904491af87606c7";
85+ sha256 = "1wd0gkx8a4x6n5cdkra96zvpv8l49i0z90amvrng70ybnzbxpfi5";
86+ }) {};
87+
88+ gargoyle = doJailbreak (self.callCabal2nix "gargoyle" (gargoyle-src + /gargoyle) {});
89+ gargoyle-postgresql-nix = pkgs.haskell.lib.addBuildTool
90+ (self.callCabal2nix "gargoyle-postgresql-nix" (gargoyle-src + /gargoyle-postgresql-nix) {})
91+ pkgs.postgresql; # `staticWhich` requires `psql` on PATH during build time
92+ gargoyle-postgresql = addBuildDepend (doJailbreak (self.callCabal2nix "gargoyle-postgresql" (gargoyle-src + /gargoyle-postgresql) {})) pkgs.postgresql;
93+ postgresql-libpq = enableCabalFlag (overrideCabal super.postgresql-libpq (drv: {
94+ pkgconfigDepends = (drv.pkgconfigDepends or []) ++ [pkgs.postgresql]; #TODO: Obelisk should probably provide this version of postgresql
95+ })) "use-pkg-config";
96+ })
97+
98+ # Add obelisk packages
99+ (self: super: let
100+ pkgs = self.callPackage ({ pkgs }: pkgs) {};
101+ in {
134− obelisk-executable-config = executableConfig.haskellPackage self;
102+ obelisk-executable-config = pkgs.obeliskExecutableConfig.haskellPackage self;
135− obelisk-executable-config-inject = executableConfig.platforms.web.inject self;
103+ obelisk-executable-config-inject = pkgs.obeliskExecutableConfig.platforms.web.inject self;
104+
105+ obelisk-asset-manifest = self.callCabal2nix "obelisk-asset-manifest" (hackGet ./lib/asset + "/manifest") {};
106+ obelisk-asset-serve-snap = self.callCabal2nix "obelisk-asset-serve-snap" (hackGet ./lib/asset + "/serve-snap") {};
107+ obelisk-backend = self.callCabal2nix "obelisk-backend" (cleanSource ./lib/backend) {};
108+ obelisk-cliapp = self.callCabal2nix "obelisk-cliapp" (cleanSource ./lib/cliapp) {};
136− obelisk-frontend = self.callCabal2nix "obelisk-frontend" (cleanSource ./lib/frontend) {};
109+ obelisk-command = self.callCabal2nix "obelisk-command" (cleanSource ./lib/command) {};
137− obelisk-migration = self.callCabal2nix "obelisk-migration" (cleanSource ./lib/migration) {};
110+ obelisk-frontend = self.callCabal2nix "obelisk-frontend" (cleanSource ./lib/frontend) {};
138− obelisk-run = self.callCabal2nix "obelisk-run" (cleanSource ./lib/run) {};
111+ obelisk-run = self.callCabal2nix "obelisk-run" (cleanSource ./lib/run) {};
139− obelisk-route = self.callCabal2nix "obelisk-route" (cleanSource ./lib/route) {};
112+ obelisk-route = self.callCabal2nix "obelisk-route" (cleanSource ./lib/route) {};
140− obelisk-selftest = self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {};
113+ obelisk-selftest = self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {};
141− obelisk-snap = self.callCabal2nix "obelisk-snap" (cleanSource ./lib/snap) {};
114+ obelisk-snap = self.callCabal2nix "obelisk-snap" (cleanSource ./lib/snap) {};
142− obelisk-snap-extras = self.callCabal2nix "obelisk-snap-extras" (cleanSource ./lib/snap-extras) {};
115+ obelisk-snap-extras = self.callCabal2nix "obelisk-snap-extras" (cleanSource ./lib/snap-extras) {};
116+ })
117+
118+ (self: super: let
119+ pkgs = self.callPackage ({ pkgs }: pkgs) {};
120+ haskellLib = pkgs.haskell.lib;
121+ #TODO: Upstream
122+ # Modify a Haskell package to add completion scripts for the given
123+ # executable produced by it. These completion scripts will be picked up
124+ # automatically if the resulting derivation is installed, e.g. by
125+ # `nix-env -i`.
126+ addOptparseApplicativeCompletionScripts = exeName: pkg: haskellLib.overrideCabal pkg (drv: {
127+ postInstall = (drv.postInstall or "") + ''
128+ BASH_COMP_DIR="$out/share/bash-completion/completions"
129+ mkdir -p "$BASH_COMP_DIR"
130+ "$out/bin/${exeName}" --bash-completion-script "$out/bin/${exeName}" >"$BASH_COMP_DIR/ob"
131+
132+ ZSH_COMP_DIR="$out/share/zsh/vendor-completions"
133+ mkdir -p "$ZSH_COMP_DIR"
134+ "$out/bin/${exeName}" --zsh-completion-script "$out/bin/${exeName}" >"$ZSH_COMP_DIR/_ob"
135+
136+ FISH_COMP_DIR="$out/share/fish/vendor_completions.d"
137+ mkdir -p "$FISH_COMP_DIR"
138+ "$out/bin/${exeName}" --fish-completion-script "$out/bin/${exeName}" >"$FISH_COMP_DIR/ob.fish"
139+ '';
140+ });
141+ in {
142+ # Dynamic linking with split objects dramatically increases startup time (about
143+ # 0.5 seconds on a decent machine with SSD), so we do `justStaticExecutables`.
144+ obelisk-command = (addOptparseApplicativeCompletionScripts "ob"
145+ (haskellLib.justStaticExecutables super.obelisk-command)).overrideAttrs (drv: {
146+ buildInputs = drv.buildInputs ++ [ pkgs.makeWrapper ];
147+ postFixup = ''
148+ ${drv.postFixup or ""}
149+ # Make `ob` reference its runtime dependencies.
150+ wrapProgram "$out"/bin/ob --prefix PATH : ${pkgs.lib.makeBinPath (commandRuntimeDeps pkgs)}
151+ '';
152+ });
153+ obelisk-selftest = haskellLib.justStaticExecutables super.obelisk-selftest;
154+ })
155+
156+ ];
143 };
157 };
144
158
159+ reflex-platform = getReflexPlatform' false system;
160+ inherit (reflex-platform) hackGet nixpkgs;
161+ pkgs = nixpkgs;
162+
163+ # The haskell environment used to build Obelisk itself, e.g. the 'ob' command
164+ ghcObelisk = reflex-platform.ghc;
165+
145 inherit (import ./lib/asset/assets.nix { inherit nixpkgs; }) mkAssets;
166 inherit (import ./lib/asset/assets.nix { inherit nixpkgs; }) mkAssets;
146
167
147− defaultHaskellOverrides = composeExtensions fixUpstreamPkgs addLibs;
148−in
149−with pkgs.lib;
168+ haskellLib = pkgs.haskell.lib;
169+
150−rec {
170+in rec {
151 inherit reflex-platform;
171 inherit reflex-platform;
152 inherit (reflex-platform) nixpkgs pinBuildInputs;
172 inherit (reflex-platform) nixpkgs pinBuildInputs;
173+ inherit (nixpkgs) lib;
153 pathGit = ./.; # Used in CI by the migration graph hash algorithm to correctly ignore files.
174 pathGit = ./.; # Used in CI by the migration graph hash algorithm to correctly ignore files.
154 path = reflex-platform.filterGit ./.;
175 path = reflex-platform.filterGit ./.;
155 obelisk = ghcObelisk;
176 obelisk = ghcObelisk;
156− commandWithMigration = ghcObelisk.obelisk-command.overrideAttrs (drv: {
177+ command = ghcObelisk.obelisk-command;
157− postInstall = (drv.postInstall or "") +
158− ''cp -r ${./migration} $out/migration;'';
159− });
160− command = pkgs.runCommand commandWithMigration.name { nativeBuildInputs = [pkgs.makeWrapper]; } ''
161− mkdir -p "$out/bin"
162− ln -s '${commandWithMigration}/bin/ob' "$out/bin/ob"
163− wrapProgram "$out"/bin/ob --prefix PATH : ${pkgs.lib.makeBinPath (commandRuntimeDeps pkgs)}
164− '';
165 shell = pinBuildInputs "obelisk-shell" ([command] ++ commandRuntimeDeps pkgs) [];
178 shell = pinBuildInputs "obelisk-shell" ([command] ++ commandRuntimeDeps pkgs) [];
166
179
167 selftest = pkgs.writeScript "selftest" ''
180 selftest = pkgs.writeScript "selftest" ''
@@ -170,7 +183,7 @@ rec {
170
183
171 PATH="${command}/bin:$PATH"
184 PATH="${command}/bin:$PATH"
172 export OBELISK_IMPL="${hackGet ./.}"
185 export OBELISK_IMPL="${hackGet ./.}"
173− "${justStaticExecutables' ghcObelisk.obelisk-selftest}/bin/obelisk-selftest" "$@"
186+ "${ghcObelisk.obelisk-selftest}/bin/obelisk-selftest" "$@"
174 '';
187 '';
175 #TODO: Why can't I build ./skeleton directly as a derivation? `nix-build -E ./.` doesn't work
188 #TODO: Why can't I build ./skeleton directly as a derivation? `nix-build -E ./.` doesn't work
176 skeleton = pkgs.runCommand "skeleton" {
189 skeleton = pkgs.runCommand "skeleton" {
@@ -178,10 +191,9 @@ rec {
178 } ''
191 } ''
179 ln -s "$dir" "$out"
192 ln -s "$dir" "$out"
180 '';
193 '';
181− nullIfAbsent = p: if pathExists p then p else null;
194+ nullIfAbsent = p: if lib.pathExists p then p else null;
182− haskellOverrides = addLibs;
183 #TODO: Avoid copying files within the nix store. Right now, obelisk-asset-manifest-generate copies files into a big blob so that the android/ios static assets can be imported from there; instead, we should get everything lined up right before turning it into an APK, so that copies, if necessary, only exist temporarily.
195 #TODO: Avoid copying files within the nix store. Right now, obelisk-asset-manifest-generate copies files into a big blob so that the android/ios static assets can be imported from there; instead, we should get everything lined up right before turning it into an APK, so that copies, if necessary, only exist temporarily.
184− processAssets = { src, packageName ? "static", moduleName ? "Static" }: pkgs.runCommand "asset-manifest" {
196+ processAssets = { src, packageName ? "obelisk-generated-static", moduleName ? "Obelisk.Generated.Static" }: pkgs.runCommand "asset-manifest" {
185 inherit src;
197 inherit src;
186 outputs = [ "out" "haskellManifest" "symlinked" ];
198 outputs = [ "out" "haskellManifest" "symlinked" ];
187 buildInputs = [
199 buildInputs = [
@@ -196,7 +208,7 @@ rec {
196 compressedJs = frontend: pkgs.runCommand "compressedJs" { buildInputs = [ pkgs.closurecompiler ]; } ''
208 compressedJs = frontend: pkgs.runCommand "compressedJs" { buildInputs = [ pkgs.closurecompiler ]; } ''
197 mkdir $out
209 mkdir $out
198 cd $out
210 cd $out
199− ln -s "${justStaticExecutables frontend}/bin/frontend.jsexe/all.js" all.unminified.js
211+ ln -s "${haskellLib.justStaticExecutables frontend}/bin/frontend.jsexe/all.js" all.unminified.js
200 closure-compiler --externs "${reflex-platform.ghcjsExternsJs}" -O ADVANCED --jscomp_warning=checkVars --create_source_map="all.js.map" --source_map_format=V3 --js_output_file="all.js" all.unminified.js
212 closure-compiler --externs "${reflex-platform.ghcjsExternsJs}" -O ADVANCED --jscomp_warning=checkVars --create_source_map="all.js.map" --source_map_format=V3 --js_output_file="all.js" all.unminified.js
201 echo "//# sourceMappingURL=all.js.map" >> all.js
213 echo "//# sourceMappingURL=all.js.map" >> all.js
202 '';
214 '';
@@ -272,13 +284,13 @@ rec {
272 pkgs.runCommand "serverExe" {} ''
284 pkgs.runCommand "serverExe" {} ''
273 mkdir $out
285 mkdir $out
274 set -eux
286 set -eux
275− ln -s "${justStaticExecutables backend}"/bin/* $out/
287+ ln -s "${haskellLib.justStaticExecutables backend}"/bin/* $out/
276 ln -s "${mkAssets assets}" $out/static.assets
288 ln -s "${mkAssets assets}" $out/static.assets
277 cp -r ${config} $out/config
289 cp -r ${config} $out/config
278 ln -s ${mkAssets (compressedJs frontend)} $out/frontend.jsexe.assets
290 ln -s ${mkAssets (compressedJs frontend)} $out/frontend.jsexe.assets
279 '';
291 '';
280
292
281− server = { exe, hostName, adminEmail, routeHost, enableHttps }@args:
293+ server = { exe, hostName, adminEmail, routeHost, enableHttps, config }@args:
282 let
294 let
283 nixos = import (pkgs.path + /nixos);
295 nixos = import (pkgs.path + /nixos);
284 in nixos {
296 in nixos {
@@ -295,13 +307,12 @@ rec {
295 # An Obelisk project is a reflex-platform project with a predefined layout and role for each component
307 # An Obelisk project is a reflex-platform project with a predefined layout and role for each component
296 project = base: projectDefinition:
308 project = base: projectDefinition:
297 let configPath = base + "/config";
309 let configPath = base + "/config";
298− static = base + "/static";
299− processedStatic = processAssets { src = static; };
300 projectOut = sys: (getReflexPlatform sys).project (args@{ nixpkgs, ... }:
310 projectOut = sys: (getReflexPlatform sys).project (args@{ nixpkgs, ... }:
301 let mkProject = { android ? null #TODO: Better error when missing
311 let mkProject = { android ? null #TODO: Better error when missing
302 , ios ? null #TODO: Better error when missing
312 , ios ? null #TODO: Better error when missing
303 , packages ? {}
313 , packages ? {}
304 , overrides ? _: _: {}
314 , overrides ? _: _: {}
315+ , staticFiles ? base + /static
305 , tools ? _: []
316 , tools ? _: []
306 , shellToolOverrides ? _: _: {}
317 , shellToolOverrides ? _: _: {}
307 , withHoogle ? false # Setting this to `true` makes shell reloading far slower
318 , withHoogle ? false # Setting this to `true` makes shell reloading far slower
@@ -309,21 +320,20 @@ rec {
309 let frontendName = "frontend";
320 let frontendName = "frontend";
310 backendName = "backend";
321 backendName = "backend";
311 commonName = "common";
322 commonName = "common";
312− staticName = "static";
323+ staticName = "obelisk-generated-static";
313− staticPath = base + "/static";
324+ processedStatic = processAssets { src = staticFiles; };
314 # The packages whose names and roles are defined by this package
325 # The packages whose names and roles are defined by this package
315− predefinedPackages = filterAttrs (_: x: x != null) {
326+ predefinedPackages = lib.filterAttrs (_: x: x != null) {
316 ${frontendName} = nullIfAbsent (base + "/frontend");
327 ${frontendName} = nullIfAbsent (base + "/frontend");
317 ${commonName} = nullIfAbsent (base + "/common");
328 ${commonName} = nullIfAbsent (base + "/common");
318 ${backendName} = nullIfAbsent (base + "/backend");
329 ${backendName} = nullIfAbsent (base + "/backend");
319 };
330 };
320 combinedPackages = predefinedPackages // packages;
331 combinedPackages = predefinedPackages // packages;
321 projectOverrides = self: super: {
332 projectOverrides = self: super: {
322− ${staticName} = dontHaddock (self.callCabal2nix "static" processedStatic.haskellManifest {});
333+ ${staticName} = haskellLib.dontHaddock (self.callCabal2nix staticName processedStatic.haskellManifest {});
323− ${backendName} = addBuildDepend super.${backendName} self.obelisk-run;
334+ ${backendName} = haskellLib.addBuildDepend super.${backendName} self.obelisk-run;
324 };
335 };
325− totalOverrides = composeExtensions (composeExtensions defaultHaskellOverrides projectOverrides) overrides;
336+ totalOverrides = lib.composeExtensions projectOverrides overrides;
326− inherit (nixpkgs) lib;
327 inherit (lib.strings) hasPrefix;
337 inherit (lib.strings) hasPrefix;
328 privateConfigDirs = ["config/backend"];
338 privateConfigDirs = ["config/backend"];
329 injectableConfig = builtins.filterSource (path: _:
339 injectableConfig = builtins.filterSource (path: _:
@@ -334,16 +344,16 @@ rec {
334 overrides = totalOverrides;
344 overrides = totalOverrides;
335 packages = combinedPackages;
345 packages = combinedPackages;
336 shells = {
346 shells = {
337− ghcSavedSplices = (filter (x: hasAttr x combinedPackages) [
347+ ghcSavedSplices = (lib.filter (x: lib.hasAttr x combinedPackages) [
338 commonName
348 commonName
339 frontendName
349 frontendName
340 ]);
350 ]);
341− ghc = (filter (x: hasAttr x combinedPackages) [
351+ ghc = (lib.filter (x: lib.hasAttr x combinedPackages) [
342 backendName
352 backendName
343 commonName
353 commonName
344 frontendName
354 frontendName
345 ]);
355 ]);
346− ghcjs = filter (x: hasAttr x combinedPackages) [
356+ ghcjs = lib.filter (x: lib.hasAttr x combinedPackages) [
347 frontendName
357 frontendName
348 commonName
358 commonName
349 ];
359 ];
@@ -351,33 +361,32 @@ rec {
351 android = {
361 android = {
352 ${if android == null then null else frontendName} = {
362 ${if android == null then null else frontendName} = {
353 executableName = "frontend";
363 executableName = "frontend";
354− ${if builtins.pathExists staticPath then "assets" else null} =
364+ ${if builtins.pathExists staticFiles then "assets" else null} =
355− executableConfig.platforms.android.inject injectableConfig processedStatic.symlinked;
365+ nixpkgs.obeliskExecutableConfig.platforms.android.inject injectableConfig processedStatic.symlinked;
356 } // android;
366 } // android;
357 };
367 };
358 ios = {
368 ios = {
359 ${if ios == null then null else frontendName} = {
369 ${if ios == null then null else frontendName} = {
360 executableName = "frontend";
370 executableName = "frontend";
361− ${if builtins.pathExists staticPath then "staticSrc" else null} =
371+ ${if builtins.pathExists staticFiles then "staticSrc" else null} =
362− executableConfig.platforms.ios.inject injectableConfig processedStatic.symlinked;
372+ nixpkgs.obeliskExecutableConfig.platforms.ios.inject injectableConfig processedStatic.symlinked;
363 } // ios;
373 } // ios;
364 };
374 };
375+ passthru = { inherit android ios packages overrides tools shellToolOverrides withHoogle staticFiles; };
365 };
376 };
366 in mkProject (projectDefinition args));
377 in mkProject (projectDefinition args));
367− serverOn = sys: config: serverExe (projectOut sys).ghc.backend (projectOut system).ghcjs.frontend static config;
378+ serverOn = sys: config: serverExe (projectOut sys).ghc.backend (projectOut system).ghcjs.frontend (projectOut sys).passthru.staticFiles config;
368− # `exe` is project's backend executable, with frontend assets, config, etc.
369− # `linuxExe` is the same but built for x86_64-linux.
370− exe = serverOn system configPath;
371− linuxExe = serverOn "x86_64-linux" configPath;
379+ linuxExe = serverOn "x86_64-linux";
372 in projectOut system // {
380 in projectOut system // {
373− inherit exe linuxExe;
381+ linuxExeConfigurable = linuxExe;
382+ linuxExe = linuxExe (base + "/config");
383+ exe = serverOn system (base + "/config") ;
374− server = args@{ hostName, adminEmail, routeHost, enableHttps, config }:
384+ server = args@{ hostName, adminEmail, routeHost, enableHttps, config }: let
375− server (args // { exe = serverOn "x86_64-linux" config;});
385+ injectableConfig = builtins.filterSource (path: _: !(lib.hasPrefix (toString config + "/backend") (toString path))) config;
386+ in server (args // { exe = linuxExe injectableConfig; });
376 obelisk = import (base + "/.obelisk/impl") {};
387 obelisk = import (base + "/.obelisk/impl") {};
377 };
388 };
378 haskellPackageSets = {
389 haskellPackageSets = {
379− ghc = reflex-platform.ghc.override {
390+ inherit (reflex-platform) ghc;
380− overrides = defaultHaskellOverrides;
381− };
382 };
391 };
383}
392}
lib/asset/manifest/src-bin/generate.hs+1
@@ -12,5 +12,6 @@ main = do
12 writeStaticProject paths haskellTarget $ StaticConfig
12 writeStaticProject paths haskellTarget $ StaticConfig
13 { _staticConfig_packageName = T.pack packageName
13 { _staticConfig_packageName = T.pack packageName
14 , _staticConfig_moduleName = T.pack moduleName
14 , _staticConfig_moduleName = T.pack moduleName
15+ , _staticConfig_rootPath = root
15 }
16 }
16 copyAndSymlink paths root fileTarget
17 copyAndSymlink paths root fileTarget
lib/asset/manifest/src/Obelisk/Asset/Promoted.hs+15−2
@@ -10,6 +10,7 @@ module Obelisk.Asset.Promoted
10import Obelisk.Asset.Gather
10import Obelisk.Asset.Gather
11
11
12import Data.Foldable
12import Data.Foldable
13+import qualified Data.List as L
13import Language.Haskell.TH (runQ, pprint)
14import Language.Haskell.TH (runQ, pprint)
14import Language.Haskell.TH.Syntax hiding (lift)
15import Language.Haskell.TH.Syntax hiding (lift)
15import GHC.TypeLits
16import GHC.TypeLits
@@ -27,15 +28,27 @@ import qualified Data.Map as Map
27data StaticConfig = StaticConfig
28data StaticConfig = StaticConfig
28 { _staticConfig_packageName :: Text --TODO: Better type
29 { _staticConfig_packageName :: Text --TODO: Better type
29 , _staticConfig_moduleName :: Text --TODO: Better type
30 , _staticConfig_moduleName :: Text --TODO: Better type
31+ , _staticConfig_rootPath :: FilePath
30 }
32 }
31
33
32writeStaticProject :: Map FilePath FilePath -> FilePath -> StaticConfig -> IO ()
34writeStaticProject :: Map FilePath FilePath -> FilePath -> StaticConfig -> IO ()
33writeStaticProject paths target cfg = do
35writeStaticProject paths target cfg = do
34− createDirectoryIfMissing True $ target </> "src"
36+ createDirectoryIfMissing True target
35 T.writeFile (target </> T.unpack (_staticConfig_packageName cfg) <.> "cabal") $ staticCabalFile cfg
37 T.writeFile (target </> T.unpack (_staticConfig_packageName cfg) <.> "cabal") $ staticCabalFile cfg
36 let modName = _staticConfig_moduleName cfg
38 let modName = _staticConfig_moduleName cfg
39+ (modName', moduleDirPath) = case L.uncons (reverse $ T.splitOn "." modName) of
40+ Nothing -> error $ "writeStaticProject: invalid module name " <> T.unpack modName
41+ Just (name, parents) -> (name, target </> "src" </> T.unpack (T.intercalate "/" $ reverse parents))
42+ createDirectoryIfMissing True moduleDirPath
37 modContents <- staticModuleFile modName paths
43 modContents <- staticModuleFile modName paths
38− T.writeFile (target </> "src" </> T.unpack modName <.> "hs") modContents
44+ T.writeFile (moduleDirPath </> T.unpack modName' <.> "hs") $ appendRootPath (_staticConfig_rootPath cfg) modContents
45+
46+appendRootPath :: FilePath -> Text -> Text
47+appendRootPath fp txt = T.unlines
48+ [ txt
49+ , "staticRootPath :: FilePath"
50+ , "staticRootPath = \"" <> T.pack fp <> "\""
51+ ]
39
52
40staticCabalFile :: StaticConfig -> Text
53staticCabalFile :: StaticConfig -> Text
41staticCabalFile cfg = T.unlines
54staticCabalFile cfg = T.unlines
lib/backend/obelisk-backend.cabal+1
@@ -7,6 +7,7 @@ library
7 hs-source-dirs: src
7 hs-source-dirs: src
8 build-depends: base,
8 build-depends: base,
9 bytestring,
9 bytestring,
10+ categories,
10 data-default,
11 data-default,
11 dependent-sum,
12 dependent-sum,
12 dependent-sum-template,
13 dependent-sum-template,
lib/backend/src/Obelisk/Backend.hs+19−16
@@ -18,8 +18,11 @@ module Obelisk.Backend
18 , runBackend
18 , runBackend
19 ) where
19 ) where
20
20
21+import Prelude hiding (id, (.))
22+import Control.Category
21import Control.Monad
23import Control.Monad
22import Control.Monad.Except
24import Control.Monad.Except
25+import Control.Categorical.Bifunctor
23import Data.ByteString (ByteString)
26import Data.ByteString (ByteString)
24import qualified Data.ByteString.Char8 as BSC8
27import qualified Data.ByteString.Char8 as BSC8
25import Data.Default (Default (..))
28import Data.Default (Default (..))
@@ -40,7 +43,7 @@ import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog
40import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
43import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
41
44
42data Backend backendRoute frontendRoute = Backend
45data Backend backendRoute frontendRoute = Backend
43− { _backend_routeEncoder :: Encoder (Either Text) (Either Text) (R (Sum backendRoute (ObeliskRoute frontendRoute))) PageName
46+ { _backend_routeEncoder :: Encoder (Either Text) Identity (R (Sum backendRoute (ObeliskRoute frontendRoute))) PageName
44 , _backend_run :: ((R backendRoute -> Snap ()) -> IO ()) -> IO ()
47 , _backend_run :: ((R backendRoute -> Snap ()) -> IO ()) -> IO ()
45 }
48 }
46
49
@@ -52,8 +55,8 @@ data GhcjsApp route = GhcjsApp
52
55
53-- | Serve a frontend, which must be the same frontend that Obelisk has built and placed in the default location
56-- | Serve a frontend, which must be the same frontend that Obelisk has built and placed in the default location
54--TODO: The frontend should be provided together with the asset paths so that this isn't so easily breakable; that will probably make this function obsolete
57--TODO: The frontend should be provided together with the asset paths so that this isn't so easily breakable; that will probably make this function obsolete
55−serveDefaultObeliskApp :: MonadSnap m => Frontend (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
58+serveDefaultObeliskApp :: MonadSnap m => ([Text] -> m ()) -> Frontend (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
56−serveDefaultObeliskApp frontend = serveObeliskApp defaultStaticAssets frontendApp
59+serveDefaultObeliskApp serveStaticAsset frontend = serveObeliskApp serveStaticAsset frontendApp
57 where frontendApp = GhcjsApp
60 where frontendApp = GhcjsApp
58 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
61 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
59 , _ghcjsApp_value = frontend
62 , _ghcjsApp_value = frontend
@@ -89,25 +92,26 @@ runSnapWithCommandLineArgs a = do
89 -- Start the web server
92 -- Start the web server
90 httpServe httpConf a
93 httpServe httpConf a
91
94
92−getPageName :: (MonadSnap m, MonadError Text parse) => m (parse PageName)
95+getPageName :: (MonadSnap m) => m PageName
93getPageName = do
96getPageName = do
94 p <- getsRequest rqPathInfo
97 p <- getsRequest rqPathInfo
95 q <- getsRequest rqQueryString
98 q <- getsRequest rqQueryString
96− return $ _validEncoder_decode pageNameValidEncoder
99+ let pageNameEncoder' :: Encoder Identity Identity PageName (String, String)
100+ pageNameEncoder' = bimap
101+ (unpackTextEncoder . pathSegmentsTextEncoder . listToNonEmptyEncoder)
102+ (unpackTextEncoder . queryParametersTextEncoder . toListMapEncoder)
97− ( "/" <> T.unpack (decodeUtf8 p)
103+ return $ decode pageNameEncoder' (T.unpack (decodeUtf8 p), T.unpack (decodeUtf8 q))
98− , "?" <> T.unpack (decodeUtf8 q)
99− )
100
104
101−getRouteWith :: (MonadSnap m, MonadError Text parse) => ValidEncoder parse route PageName -> m (parse route)
105+getRouteWith :: (MonadSnap m) => Encoder Identity parse route PageName -> m (parse route)
102getRouteWith e = do
106getRouteWith e = do
103 pageName <- getPageName
107 pageName <- getPageName
104− return $ pageName >>= _validEncoder_decode e
108+ return $ tryDecode e pageName
105
109
106−serveObeliskApp :: MonadSnap m => StaticAssets -> GhcjsApp (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
110+serveObeliskApp :: MonadSnap m => ([Text] -> m ()) -> GhcjsApp (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
107−serveObeliskApp staticAssets frontendApp = \case
111+serveObeliskApp serveStaticAsset frontendApp = \case
108 ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp frontendApp $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
112 ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp frontendApp $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
109 ObeliskRoute_Resource resComponent :=> Identity resRest -> case resComponent :=> Identity resRest of
113 ObeliskRoute_Resource resComponent :=> Identity resRest -> case resComponent :=> Identity resRest of
110− ResourceRoute_Static :=> Identity pathSegments -> serveStaticAssets staticAssets pathSegments
114+ ResourceRoute_Static :=> Identity pathSegments -> serveStaticAsset pathSegments
111 ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp frontendApp $ GhcjsAppRoute_Resource :/ pathSegments
115 ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp frontendApp $ GhcjsAppRoute_Resource :/ pathSegments
112 ResourceRoute_JSaddleWarp :=> Identity _ -> do
116 ResourceRoute_JSaddleWarp :=> Identity _ -> do
113 let msg = "Error: Obelisk.Backend received jsaddle request"
117 let msg = "Error: Obelisk.Backend received jsaddle request"
@@ -140,10 +144,9 @@ runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend
140 Right validFullEncoder -> _backend_run backend $ \serveRoute -> do
144 Right validFullEncoder -> _backend_run backend $ \serveRoute -> do
141 runSnapWithCommandLineArgs $ do
145 runSnapWithCommandLineArgs $ do
142 getRouteWith validFullEncoder >>= \case
146 getRouteWith validFullEncoder >>= \case
143− Left e -> writeText e
144− Right r -> case r of
147+ Identity r -> case r of
145 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
148 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
146− InR obeliskRoute :=> Identity a -> serveDefaultObeliskApp frontend $ obeliskRoute :/ a
149+ InR obeliskRoute :=> Identity a -> serveDefaultObeliskApp (serveStaticAssets defaultStaticAssets) frontend $ obeliskRoute :/ a
147
150
148renderGhcjsFrontend :: route -> Frontend route -> IO ByteString
151renderGhcjsFrontend :: route -> Frontend route -> IO ByteString
149renderGhcjsFrontend route f = do
152renderGhcjsFrontend route f = do
lib/cliapp/obelisk-cliapp.cabal+1
@@ -12,6 +12,7 @@ library
12 , bytestring
12 , bytestring
13 , exceptions
13 , exceptions
14 , io-streams
14 , io-streams
15+ , lens
15 , logging-effect
16 , logging-effect
16 , monad-loops
17 , monad-loops
17 , mtl
18 , mtl
lib/cliapp/src/Obelisk/CliApp.hs+9−2
@@ -6,7 +6,8 @@
6module Obelisk.CliApp
6module Obelisk.CliApp
7 (
7 (
8 -- .Types
8 -- .Types
9− Cli
9+ CliLog
10+ , CliThrow
10 , CliT(..)
11 , CliT(..)
11 , runCli
12 , runCli
12 , CliConfig
13 , CliConfig
@@ -20,22 +21,28 @@ module Obelisk.CliApp
20 , withSpinner'
21 , withSpinner'
21
22
22 -- .Logging
23 -- .Logging
24+ , AsUnstructuredError (..)
23 , newCliConfig
25 , newCliConfig
24 , getLogLevel
26 , getLogLevel
25 , putLog
27 , putLog
26 , failWith
28 , failWith
29+ , errorToWarning
27 , withExitFailMessage
30 , withExitFailMessage
28
31
29 -- Control.Monad.Log
32 -- Control.Monad.Log
30 , Severity (..)
33 , Severity (..)
31
34
32 -- .Process
35 -- .Process
33− , ProcessFailed (..)
36+ , ProcessFailure (..)
37+ , AsProcessFailure (..)
34 , readProcessAndLogStderr
38 , readProcessAndLogStderr
39+ , readProcessAndLogOutput
40+ , readCreateProcessWithExitCode
35 , callProcessAndLogOutput
41 , callProcessAndLogOutput
36 , createProcess_
42 , createProcess_
37 , callProcess
43 , callProcess
38 , callCommand
44 , callCommand
45+ , reconstructCommand
39 ) where
46 ) where
40
47
41import Control.Monad.Log (Severity (..))
48import Control.Monad.Log (Severity (..))
lib/cliapp/src/Obelisk/CliApp/Demo.hs+5−2
@@ -13,7 +13,10 @@ import Control.Monad.Catch (MonadMask)
13
13
14import Obelisk.CliApp
14import Obelisk.CliApp
15
15
16+cliDemo
16−cliDemo :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m) => m ()
17+ :: ( MonadIO m, MonadMask m
18+ , CliLog m, HasCliConfig e m, CliThrow e m, AsProcessFailure e, AsUnstructuredError e)
19+ => m ()
17cliDemo = withSpinner "CLI Demo" $ do
20cliDemo = withSpinner "CLI Demo" $ do
18 putLog Notice "This demo will showcase the CLI library functionality"
21 putLog Notice "This demo will showcase the CLI library functionality"
19 _ <- withSpinner' "Searching long for something" (Just $ \c -> "Discovered " <> T.pack (show c) <> " objects") $ do
22 _ <- withSpinner' "Searching long for something" (Just $ \c -> "Discovered " <> T.pack (show c) <> " objects") $ do
@@ -40,7 +43,7 @@ cliDemo = withSpinner "CLI Demo" $ do
40 withSpinner "Looking around" $ do
43 withSpinner "Looking around" $ do
41 delay
44 delay
42 output <- readProcessAndLogStderr Notice $ proc "ls" ["-l", "/"]
45 output <- readProcessAndLogStderr Notice $ proc "ls" ["-l", "/"]
43− putLog Notice $ "Output was: " <> T.pack output
46+ putLog Notice $ "Output was: " <> output
44 delay
47 delay
45 callProcessAndLogOutput (Notice, Error) $ proc "ls" ["-l", "/does-not-exist"]
48 callProcessAndLogOutput (Notice, Error) $ proc "ls" ["-l", "/does-not-exist"]
46 delay
49 delay
lib/cliapp/src/Obelisk/CliApp/Logging.hs+66−29
@@ -4,15 +4,36 @@
4{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE LambdaCase #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE RankNTypes #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
8-- | Provides a logging handler that facilitates safe ouputting to terminal using MVar based locking.
9-- | Provides a logging handler that facilitates safe ouputting to terminal using MVar based locking.
9-- | Spinner.hs and Process.hs work on this guarantee.
10-- | Spinner.hs and Process.hs work on this guarantee.
10−module Obelisk.CliApp.Logging where
11+module Obelisk.CliApp.Logging
12+ ( AsUnstructuredError (..)
13+ , newCliConfig
14+ , runCli
15+ , verboseLogLevel
16+ , isOverwrite
17+ , getSeverity
18+ , getLogLevel
19+ , setLogLevel
20+ , putLog
21+ , putLogRaw
22+ , failWith
23+ , errorToWarning
24+ , withExitFailMessage
25+ , writeLog
26+ , allowUserToMakeLoggingVerbose
27+ , getChars
28+ , fork
29+ ) where
11
30
12import Control.Concurrent (ThreadId, forkIO, killThread, threadDelay)
31import Control.Concurrent (ThreadId, forkIO, killThread, threadDelay)
13import Control.Concurrent.MVar (modifyMVar_, newMVar)
32import Control.Concurrent.MVar (modifyMVar_, newMVar)
33+import Control.Lens (Prism', review)
14import Control.Monad (unless, void, when)
34import Control.Monad (unless, void, when)
15import Control.Monad.Catch (MonadCatch, MonadMask, bracket, catch, throwM)
35import Control.Monad.Catch (MonadCatch, MonadMask, bracket, catch, throwM)
36+import Control.Monad.Except (throwError)
16import Control.Monad.IO.Class (liftIO)
37import Control.Monad.IO.Class (liftIO)
17import Control.Monad.Log (Severity (..), WithSeverity (..), logMessage, runLoggingT)
38import Control.Monad.Log (Severity (..), WithSeverity (..), logMessage, runLoggingT)
18import Control.Monad.Loops (iterateUntil)
39import Control.Monad.Loops (iterateUntil)
@@ -25,23 +46,30 @@ import qualified Data.Text.IO as T
25import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
46import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
26 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
47 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
27 SGR (SetColor, SetConsoleIntensity), clearLine)
48 SGR (SetColor, SetConsoleIntensity), clearLine)
28−import System.Exit (ExitCode (..), exitWith)
49+import System.Exit (ExitCode (..))
29import System.IO (BufferMode (NoBuffering), hFlush, hReady, hSetBuffering, stderr, stdin, stdout)
50import System.IO (BufferMode (NoBuffering), hFlush, hReady, hSetBuffering, stderr, stdin, stdout)
30
51
31import qualified Obelisk.CliApp.TerminalString as TS
52import qualified Obelisk.CliApp.TerminalString as TS
32import Obelisk.CliApp.Types
53import Obelisk.CliApp.Types
33
54
34−newCliConfig :: Severity -> Bool -> Bool -> IO CliConfig
55+newCliConfig
56+ :: Severity
57+ -> Bool
58+ -> Bool
59+ -> (e -> (Text, Int))
60+ -> IO (CliConfig e)
35−newCliConfig sev noColor noSpinner = do
61+newCliConfig sev noColor noSpinner errorLogExitCode = do
36 level <- newIORef sev
62 level <- newIORef sev
37 lock <- newMVar False
63 lock <- newMVar False
38 tipDisplayed <- newIORef False
64 tipDisplayed <- newIORef False
39 stack <- newIORef ([], [])
65 stack <- newIORef ([], [])
40− return $ CliConfig level noColor noSpinner lock tipDisplayed stack
66+ return $ CliConfig level noColor noSpinner lock tipDisplayed stack errorLogExitCode
41
67
42−runCli :: MonadIO m => CliConfig -> CliT m a -> m a
68+runCli :: MonadIO m => CliConfig e -> CliT e m a -> m a
43runCli c =
69runCli c =
44 flip runLoggingT (handleLog c)
70 flip runLoggingT (handleLog c)
71+ . flip runReaderT (_cliConfig_errorLogExitCode c)
72+ . unDieT
45 . flip runReaderT c
73 . flip runReaderT c
46 . unCliT
74 . unCliT
47
75
@@ -59,18 +87,18 @@ getSeverity = \case
59 Output_LogRaw (WithSeverity sev _) -> Just sev
87 Output_LogRaw (WithSeverity sev _) -> Just sev
60 _ -> Nothing
88 _ -> Nothing
61
89
62−setLogLevel :: (MonadIO m, HasCliConfig m) => Severity -> m ()
63−setLogLevel sev = do
64− l <- _cliConfig_logLevel <$> getCliConfig
65− liftIO $ writeIORef l sev
66−
67−getLogLevel :: (MonadIO m, HasCliConfig m) => m Severity
90+getLogLevel :: (MonadIO m, HasCliConfig e m) => m Severity
68getLogLevel = getLogLevel' =<< getCliConfig
91getLogLevel = getLogLevel' =<< getCliConfig
69
92
70−getLogLevel' :: MonadIO m => CliConfig -> m Severity
93+getLogLevel' :: MonadIO m => CliConfig e -> m Severity
71getLogLevel' = liftIO . readIORef . _cliConfig_logLevel
94getLogLevel' = liftIO . readIORef . _cliConfig_logLevel
72
95
96+setLogLevel :: (MonadIO m, HasCliConfig e m) => Severity -> m ()
97+setLogLevel sev = do
98+ l <- _cliConfig_logLevel <$> getCliConfig
99+ liftIO $ writeIORef l sev
100+
73−handleLog :: MonadIO m => CliConfig -> Output -> m ()
101+handleLog :: MonadIO m => CliConfig e -> Output -> m ()
74handleLog conf output = do
102handleLog conf output = do
75 level <- getLogLevel' conf
103 level <- getLogLevel' conf
76 liftIO $ modifyMVar_ (_cliConfig_lock conf) $ \wasOverwriting -> do
104 liftIO $ modifyMVar_ (_cliConfig_lock conf) $ \wasOverwriting -> do
@@ -107,26 +135,35 @@ handleLog' noColor output = do
107 hFlush stdout
135 hFlush stdout
108 return $ isOverwrite output
136 return $ isOverwrite output
109
137
110−-- | Log a message to the console.
111−--
112−-- Logs safely even if there are ongoing spinners.
113−putLog :: Cli m => Severity -> Text -> m ()
114−putLog sev = logMessage . Output_Log . WithSeverity sev
115−
116-- | Like `putLog` but without the implicit newline added.
138-- | Like `putLog` but without the implicit newline added.
117−putLogRaw :: Cli m => Severity -> Text -> m ()
139+putLogRaw :: CliLog m => Severity -> Text -> m ()
118putLogRaw sev = logMessage . Output_LogRaw . WithSeverity sev
140putLogRaw sev = logMessage . Output_LogRaw . WithSeverity sev
119
141
142+-- | Indicates unstructured errors form one variant (or conceptual projection)
143+-- of the error type.
144+--
145+-- Shouldn't really use this, but who has time to clean up that much!
146+class AsUnstructuredError e where
147+ asUnstructuredError :: Prism' e Text
148+
149+instance AsUnstructuredError Text where
150+ asUnstructuredError = id
151+
120-- | Like `putLog Alert` but also abrupts the program.
152-- | Like `putLog Alert` but also abrupts the program.
121−failWith :: (MonadIO m, Cli m) => Text -> m a
153+failWith :: (CliThrow e m, AsUnstructuredError e) => Text -> m a
122−failWith s = do
154+failWith = throwError . review asUnstructuredError
123− putLog Alert s
155+
124− liftIO $ exitWith $ ExitFailure 2
156+errorToWarning
157+ :: (HasCliConfig e m, CliThrow e m, CliLog m)
158+ => e -> m ()
159+errorToWarning e = do
160+ c <- getCliConfig
161+ putLog Warning $ fst $ _cliConfig_errorLogExitCode c e
125
162
126-- | Intercept ExitFailure exceptions and log the given alert before exiting.
163-- | Intercept ExitFailure exceptions and log the given alert before exiting.
127--
164--
128-- This is useful when you want to provide contextual information to a deeper failure.
165-- This is useful when you want to provide contextual information to a deeper failure.
129−withExitFailMessage :: (Cli m, MonadCatch m) => Text -> m a -> m a
166+withExitFailMessage :: (CliLog m, MonadCatch m) => Text -> m a -> m a
130withExitFailMessage msg f = f `catch` \(e :: ExitCode) -> do
167withExitFailMessage msg f = f `catch` \(e :: ExitCode) -> do
131 case e of
168 case e of
132 ExitFailure _ -> putLog Alert msg
169 ExitFailure _ -> putLog Alert msg
@@ -134,7 +171,7 @@ withExitFailMessage msg f = f `catch` \(e :: ExitCode) -> do
134 throwM e
171 throwM e
135
172
136-- | Write log to stdout, with colors (unless `noColor`)
173-- | Write log to stdout, with colors (unless `noColor`)
137−writeLog:: (MonadIO m, MonadMask m) => Bool -> Bool -> WithSeverity Text -> m ()
174+writeLog :: (MonadIO m, MonadMask m) => Bool -> Bool -> WithSeverity Text -> m ()
138writeLog withNewLine noColor (WithSeverity severity s)
175writeLog withNewLine noColor (WithSeverity severity s)
139 | noColor && severity <= Warning = liftIO $ putFn $ T.pack (show severity) <> ": " <> s
176 | noColor && severity <= Warning = liftIO $ putFn $ T.pack (show severity) <> ": " <> s
140 | not noColor && severity <= Error = TS.putStrWithSGR errorColors h withNewLine s
177 | not noColor && severity <= Error = TS.putStrWithSGR errorColors h withNewLine s
@@ -152,7 +189,7 @@ writeLog withNewLine noColor (WithSeverity severity s)
152--
189--
153-- Call this function in a thread, and kill it to turn off keystroke monitoring.
190-- Call this function in a thread, and kill it to turn off keystroke monitoring.
154allowUserToMakeLoggingVerbose
191allowUserToMakeLoggingVerbose
155− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
192+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
156 => String -- ^ The key to press in order to make logging verbose
193 => String -- ^ The key to press in order to make logging verbose
157 -> m ()
194 -> m ()
158allowUserToMakeLoggingVerbose keyCode = bracket showTip (liftIO . killThread) $ \_ -> do
195allowUserToMakeLoggingVerbose keyCode = bracket showTip (liftIO . killThread) $ \_ -> do
@@ -185,7 +222,7 @@ getChars = reverse <$> f mempty
185 True -> f (x:xs)
222 True -> f (x:xs)
186 False -> return (x:xs)
223 False -> return (x:xs)
187
224
188−fork :: (HasCliConfig m, MonadIO m) => CliT IO () -> m ThreadId
225+fork :: (HasCliConfig e m, MonadIO m) => CliT e IO () -> m ThreadId
189fork f = do
226fork f = do
190 c <- getCliConfig
227 c <- getCliConfig
191 liftIO $ forkIO $ runCli c f
228 liftIO $ forkIO $ runCli c f
lib/cliapp/src/Obelisk/CliApp/Process.hs+84−32
@@ -9,25 +9,30 @@
9-- | An extension of `System.Process` that integrates with logging (`Obelisk.CLI.Logging`)
9-- | An extension of `System.Process` that integrates with logging (`Obelisk.CLI.Logging`)
10-- and is thus spinner friendly.
10-- and is thus spinner friendly.
11module Obelisk.CliApp.Process
11module Obelisk.CliApp.Process
12− ( ProcessFailed (..)
12+ ( ProcessFailure (..)
13+ , AsProcessFailure (..)
13 , readProcessAndLogStderr
14 , readProcessAndLogStderr
15+ , readProcessAndLogOutput
16+ , readCreateProcessWithExitCode
14 , callProcessAndLogOutput
17 , callProcessAndLogOutput
15 , createProcess
18 , createProcess
16 , createProcess_
19 , createProcess_
17 , callProcess
20 , callProcess
18 , callCommand
21 , callCommand
22+ , reconstructCommand
19 ) where
23 ) where
20
24
21−import Control.Applicative (liftA2)
25+import Control.Monad ((<=<), join, void)
22−import Control.Monad (void, (<=<))
23−import Control.Monad.Catch (Exception, MonadMask, throwM)
26+import Control.Monad.Except (throwError)
24import Control.Monad.IO.Class (MonadIO, liftIO)
27import Control.Monad.IO.Class (MonadIO, liftIO)
28+import Control.Lens (Prism', review)
29+import qualified Data.ByteString as BS
25import qualified Data.ByteString.Char8 as BSC
30import qualified Data.ByteString.Char8 as BSC
26import Data.Function (fix)
31import Data.Function (fix)
27import Data.Monoid ((<>))
32import Data.Monoid ((<>))
33+import Data.Text (Text)
28import qualified Data.Text as T
34import qualified Data.Text as T
29−import Data.Text.Encoding (decodeUtf8)
35+import qualified Data.Text.Encoding as T
30−import qualified Data.Text.IO as T
31import System.Exit (ExitCode (..))
36import System.Exit (ExitCode (..))
32import System.IO (Handle)
37import System.IO (Handle)
33import System.IO.Streams (InputStream, handleToInputStream)
38import System.IO.Streams (InputStream, handleToInputStream)
@@ -39,58 +44,97 @@ import qualified System.Process as Process
39
44
40import Control.Monad.Log (Severity (..))
45import Control.Monad.Log (Severity (..))
41import Obelisk.CliApp.Logging (putLog, putLogRaw)
46import Obelisk.CliApp.Logging (putLog, putLogRaw)
42−import Obelisk.CliApp.Types (Cli)
47+import Obelisk.CliApp.Types (CliLog, CliThrow)
43
48
49+-- TODO put back in `Obelisk.CliApp.Process` and use prisms for extensible exceptions
44−data ProcessFailed = ProcessFailed Process.CmdSpec Int -- exit code
50+data ProcessFailure = ProcessFailure Process.CmdSpec Int -- exit code
45 deriving Show
51 deriving Show
46
52
47−instance Exception ProcessFailed
53+-- | Indicates arbitrary process failures form one variant (or conceptual projection) of
54+-- the error type.
55+class AsProcessFailure e where
56+ asProcessFailure :: Prism' e ProcessFailure
57+
58+instance AsProcessFailure ProcessFailure where
59+ asProcessFailure = id
48
60
49readProcessAndLogStderr
61readProcessAndLogStderr
50− :: (MonadIO m, MonadMask m, Cli m)
62+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
51− => Severity -> CreateProcess -> m String
63+ => Severity -> CreateProcess -> m Text
52readProcessAndLogStderr sev process = do
64readProcessAndLogStderr sev process = do
53 (out, _err) <- withProcess process $ \_out err -> do
65 (out, _err) <- withProcess process $ \_out err -> do
54 streamToLog =<< liftIO (streamHandle sev err)
66 streamToLog =<< liftIO (streamHandle sev err)
55− -- TODO: Why are we using Text here?
56− liftIO $ T.unpack . T.strip <$> T.hGetContents out
67+ liftIO $ T.decodeUtf8 <$> BS.hGetContents out
68+
69+readCreateProcessWithExitCode
70+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
71+ => CreateProcess -> m (ExitCode, String, String)
72+readCreateProcessWithExitCode process = do
73+ putLog Debug $ "Creating process: " <> reconstructCommand (cmdspec process)
74+ liftIO $ Process.readCreateProcessWithExitCode process ""
75+
76+-- | Like `System.Process.readProcess` but logs the combined output (stdout and stderr)
77+-- with the corresponding severity.
78+--
79+-- Usually this function is called as `callProcessAndLogOutput (Debug, Error)`. However
80+-- some processes are known to spit out diagnostic or informative messages in stderr, in
81+-- which case it is advisable to call it with a non-Error severity for stderr, like
82+-- `callProcessAndLogOutput (Debug, Debug)`.
83+readProcessAndLogOutput
84+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
85+ => (Severity, Severity) -> CreateProcess -> m Text
86+readProcessAndLogOutput (sev_out, sev_err) process = do
87+ (_, Just out, Just err, p) <- createProcess $ process
88+ { std_out = CreatePipe , std_err = CreatePipe }
57
89
90+ -- TODO interleave stdout and stderr in log correctly
91+ streamToLog =<< liftIO (streamHandle sev_err err)
92+ outText <- liftIO $ T.decodeUtf8 <$> BS.hGetContents out
93+ putLogRaw sev_out outText
94+
95+ liftIO (waitForProcess p) >>= \case
96+ ExitSuccess -> pure outText
97+ ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (cmdspec process) code
98+
58−-- | Like `System.Process.callProcess` but logs the combined output--stdout and stderr with the corresponding
99+-- | Like `System.Process.callProcess` but logs the combined output (stdout and stderr)
59−-- severity.
100+-- with the corresponding severity.
60--
101--
61−-- Usually this function is called as `callProcessAndLogOutput (Debug, Error)`. However some processes
102+-- Usually this function is called as `callProcessAndLogOutput (Debug, Error)`. However
62−-- are known to spit out diagnostic or informative messages in stderr, in which case it is advisable to call
103+-- some processes are known to spit out diagnostic or informative messages in stderr, in
104+-- which case it is advisable to call it with a non-Error severity for stderr, like
63−-- it with a non-Error severity for stderr, like `callProcessAndLogOutput (Debug, Debug)`.
105+-- `callProcessAndLogOutput (Debug, Debug)`.
64callProcessAndLogOutput
106callProcessAndLogOutput
65− :: (MonadIO m, MonadMask m, Cli m)
107+
108+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
66 => (Severity, Severity) -> CreateProcess -> m ()
109 => (Severity, Severity) -> CreateProcess -> m ()
67−callProcessAndLogOutput (sev_out, sev_err) process = do
110+callProcessAndLogOutput (sev_out, sev_err) process =
68 void $ withProcess process $ \out err -> do
111 void $ withProcess process $ \out err -> do
69− stream <- liftIO $ do
112+ stream <- liftIO $ join $ combineStream
70− uncurry combineStream =<< liftA2 (,) (streamHandle sev_out out) (streamHandle sev_err err)
113+ <$> streamHandle sev_out out
114+ <*> streamHandle sev_err err
71 streamToLog stream
115 streamToLog stream
72 where
116 where
73 combineStream s1 s2 = concurrentMerge [s1, s2]
117 combineStream s1 s2 = concurrentMerge [s1, s2]
74
118
75-- | Like `System.Process.createProcess` but also logs (debug) the process being run
119-- | Like `System.Process.createProcess` but also logs (debug) the process being run
76createProcess
120createProcess
77− :: (MonadIO m, Cli m)
121+ :: (MonadIO m, CliLog m)
78 => CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
122 => CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
79createProcess p = do
123createProcess p = do
80− putLog Debug $ "Creating process: " <> T.pack (show $ cmdspec p)
124+ putLog Debug $ "Creating process: " <> reconstructCommand (cmdspec p)
81 liftIO $ Process.createProcess p
125 liftIO $ Process.createProcess p
82
126
83-- | Like `System.Process.createProcess_` but also logs (debug) the process being run
127-- | Like `System.Process.createProcess_` but also logs (debug) the process being run
84createProcess_
128createProcess_
85− :: (MonadIO m, Cli m)
129+ :: (MonadIO m, CliLog m)
86 => String -> CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
130 => String -> CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
87createProcess_ name p = do
131createProcess_ name p = do
88− putLog Debug $ "Creating process " <> T.pack name <> ": " <> T.pack (show $ cmdspec p)
132+ putLog Debug $ "Creating process " <> T.pack name <> ": " <> reconstructCommand (cmdspec p)
89 liftIO $ Process.createProcess p
133 liftIO $ Process.createProcess p
90
134
91-- | Like `System.Process.callProcess` but also logs (debug) the process being run
135-- | Like `System.Process.callProcess` but also logs (debug) the process being run
92callProcess
136callProcess
93− :: (MonadIO m, Cli m)
137+ :: (MonadIO m, CliLog m)
94 => String -> [String] -> m ()
138 => String -> [String] -> m ()
95callProcess exe args = do
139callProcess exe args = do
96 putLog Debug $ "Calling process " <> T.pack exe <> " with args: " <> T.pack (show args)
140 putLog Debug $ "Calling process " <> T.pack exe <> " with args: " <> T.pack (show args)
@@ -98,14 +142,14 @@ callProcess exe args = do
98
142
99-- | Like `System.Process.callCommand` but also logs (debug) the command being run
143-- | Like `System.Process.callCommand` but also logs (debug) the command being run
100callCommand
144callCommand
101− :: (MonadIO m, Cli m)
145+ :: (MonadIO m, CliLog m)
102 => String -> m ()
146 => String -> m ()
103callCommand cmd = do
147callCommand cmd = do
104 putLog Debug $ "Calling command " <> T.pack cmd
148 putLog Debug $ "Calling command " <> T.pack cmd
105 liftIO $ Process.callCommand cmd
149 liftIO $ Process.callCommand cmd
106
150
107withProcess
151withProcess
108− :: (MonadIO m, MonadMask m, Cli m)
152+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
109 => CreateProcess -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
153 => CreateProcess -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
110withProcess process f = do -- TODO: Use bracket.
154withProcess process f = do -- TODO: Use bracket.
111 -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
155 -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
@@ -116,7 +160,7 @@ withProcess process f = do -- TODO: Use bracket.
116
160
117 liftIO (waitForProcess p) >>= \case
161 liftIO (waitForProcess p) >>= \case
118 ExitSuccess -> return (out, err)
162 ExitSuccess -> return (out, err)
119− ExitFailure code -> throwM $ ProcessFailed (cmdspec process) code
163+ ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (cmdspec process) code
120
164
121-- Create an input stream from the file handle, associating each item with the given severity.
165-- Create an input stream from the file handle, associating each item with the given severity.
122streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
166streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
@@ -124,9 +168,17 @@ streamHandle sev = Streams.map (sev,) <=< handleToInputStream
124
168
125-- | Read from an input stream and log its contents
169-- | Read from an input stream and log its contents
126streamToLog
170streamToLog
127− :: (MonadIO m, MonadMask m, Cli m)
171+ :: (MonadIO m, CliLog m)
128 => InputStream (Severity, BSC.ByteString) -> m ()
172 => InputStream (Severity, BSC.ByteString) -> m ()
129streamToLog stream = fix $ \loop -> do
173streamToLog stream = fix $ \loop -> do
130 liftIO (Streams.read stream) >>= \case
174 liftIO (Streams.read stream) >>= \case
131 Nothing -> return ()
175 Nothing -> return ()
132− Just (sev, line) -> putLogRaw sev (decodeUtf8 line) >> loop
176+ Just (sev, line) -> putLogRaw sev (T.decodeUtf8 line) >> loop
177+
178+-- | Pretty print a 'CmdSpec'
179+reconstructCommand :: Process.CmdSpec -> Text
180+reconstructCommand (Process.ShellCommand str) = T.pack str
181+reconstructCommand (Process.RawCommand c as) = processToShellString c as
182+ where
183+ processToShellString cmd args = T.unwords $ map quoteAndEscape (cmd : args)
184+ quoteAndEscape x = "'" <> T.replace "'" "'\''" (T.pack x) <> "'"
lib/cliapp/src/Obelisk/CliApp/Spinner.hs+4−4
@@ -24,11 +24,11 @@ import System.Console.ANSI (Color (Blue, Cyan, Green, Red))
24
24
25import Obelisk.CliApp.Logging (allowUserToMakeLoggingVerbose, fork, putLog)
25import Obelisk.CliApp.Logging (allowUserToMakeLoggingVerbose, fork, putLog)
26import Obelisk.CliApp.TerminalString (TerminalString (..), enquiryCode)
26import Obelisk.CliApp.TerminalString (TerminalString (..), enquiryCode)
27−import Obelisk.CliApp.Types (Cli, CliConfig (..), HasCliConfig, Output (..), getCliConfig)
27+import Obelisk.CliApp.Types (CliLog, CliConfig (..), HasCliConfig, Output (..), getCliConfig)
28
28
29-- | Run an action with a CLI spinner.
29-- | Run an action with a CLI spinner.
30withSpinner
30withSpinner
31− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
31+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
32 => Text -> m a -> m a
32 => Text -> m a -> m a
33withSpinner s = withSpinner' s $ Just $ const s
33withSpinner s = withSpinner' s $ Just $ const s
34
34
@@ -39,13 +39,13 @@ withSpinner s = withSpinner' s $ Just $ const s
39-- The 'no trail' property automatically carries over to sub-spinners (in that
39-- The 'no trail' property automatically carries over to sub-spinners (in that
40-- they won't leave a trail either).
40-- they won't leave a trail either).
41withSpinnerNoTrail
41withSpinnerNoTrail
42− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
42+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
43 => Text -> m a -> m a
43 => Text -> m a -> m a
44withSpinnerNoTrail s = withSpinner' s Nothing
44withSpinnerNoTrail s = withSpinner' s Nothing
45
45
46-- | Advanced version that controls the display and content of the trail message.
46-- | Advanced version that controls the display and content of the trail message.
47withSpinner'
47withSpinner'
48− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
48+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
49 => Text
49 => Text
50 -> Maybe (a -> Text) -- ^ Leave an optional trail with the given message creator
50 -> Maybe (a -> Text) -- ^ Leave an optional trail with the given message creator
51 -> m a
51 -> m a
lib/cliapp/src/Obelisk/CliApp/Types.hs+94−18
@@ -1,27 +1,30 @@
1{-# LANGUAGE ConstraintKinds #-}
1{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE FunctionalDependencies #-}
5+{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE UndecidableInstances #-}
6module Obelisk.CliApp.Types where
10module Obelisk.CliApp.Types where
7
11
8import Control.Concurrent.MVar (MVar)
12import Control.Concurrent.MVar (MVar)
9import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
13import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
10−import Control.Monad.Log (LoggingT, MonadLog, Severity (..), WithSeverity (..))
14+import Control.Monad.Log (LoggingT, MonadLog, Severity (..), WithSeverity (..), logMessage)
11−import Control.Monad.Reader (MonadIO, ReaderT (..), ask)
15+import Control.Monad.Reader (MonadIO, ReaderT (..), MonadReader (..), ask, mapReaderT)
16+import Control.Monad.Writer (WriterT)
17+import Control.Monad.State (StateT)
18+import Control.Monad.Except (ExceptT, MonadError (..))
19+import Control.Monad.IO.Class (liftIO)
20+import Control.Monad.Trans (MonadTrans, lift)
12import Data.IORef (IORef)
21import Data.IORef (IORef)
13import Data.Text (Text)
22import Data.Text (Text)
23+import System.Exit (ExitCode (..), exitWith)
14
24
15import Obelisk.CliApp.TerminalString (TerminalString)
25import Obelisk.CliApp.TerminalString (TerminalString)
16
26
17−data CliConfig = CliConfig
27+--------------------------------------------------------------------------------
18− { _cliConfig_logLevel :: IORef Severity -- We are capable of changing the log level at runtime
19− , _cliConfig_noColor :: Bool -- Disallow coloured output
20− , _cliConfig_noSpinner :: Bool -- Disallow spinners
21− , _cliConfig_lock :: MVar Bool -- Whether the last message was an Overwrite output
22− , _cliConfig_tipDisplayed :: IORef Bool -- Whether the user tip (to make verbose) was already displayed
23− , _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString]) -- Stack of logs from nested spinners
24− }
25
28
26data Output
29data Output
27 = Output_Log (WithSeverity Text) -- Regular logging message (with colors and newlines)
30 = Output_Log (WithSeverity Text) -- Regular logging message (with colors and newlines)
@@ -31,19 +34,92 @@ data Output
31 | Output_ClearLine -- Clear the line
34 | Output_ClearLine -- Clear the line
32 deriving (Eq, Show, Ord)
35 deriving (Eq, Show, Ord)
33
36
34−type Cli m = MonadLog Output m
37+type CliLog m = MonadLog Output m
35
38
36−newtype CliT m a = CliT
39+type CliThrow e m = MonadError e m
40+
41+-- | Log a message to the console.
42+--
43+-- Logs safely even if there are ongoing spinners.
44+putLog :: CliLog m => Severity -> Text -> m ()
45+putLog sev = logMessage . Output_Log . WithSeverity sev
46+
37− { unCliT :: ReaderT CliConfig (LoggingT Output m) a
47+newtype DieT e m a = DieT { unDieT :: ReaderT (e -> (Text, Int)) (LoggingT Output m) a }
38− }
39 deriving
48 deriving
40 ( Functor, Applicative, Monad, MonadIO
49 ( Functor, Applicative, Monad, MonadIO
41 , MonadThrow, MonadCatch, MonadMask
50 , MonadThrow, MonadCatch, MonadMask
42 , MonadLog Output
51 , MonadLog Output
43 )
52 )
44
53
54+instance MonadTrans (DieT e) where
55+ lift = DieT . lift . lift
56+
57+-- | Error printer is private to DieT
58+instance MonadReader r m => MonadReader r (DieT e m) where
59+ ask = DieT $ lift $ ask
60+ local = (\f (DieT a) -> DieT $ f a) . mapReaderT . local
61+ reader = DieT . lift . lift . reader
62+
63+-- TODO generalize to bigger error types
64+instance MonadIO m => MonadError e (DieT e m) where
65+ throwError e = do
66+ handler <- DieT ask
67+ let (output, exitCode) = handler e
68+ putLog Alert output
69+ liftIO $ exitWith $ ExitFailure exitCode
70+
71+ -- Cannot catch
72+ catchError m _ = m
73+
74+--------------------------------------------------------------------------------
75+
76+data CliConfig e = CliConfig
77+ { -- | We are capable of changing the log level at runtime
78+ _cliConfig_logLevel :: IORef Severity
79+ , -- | Disallow coloured output
80+ _cliConfig_noColor :: Bool
81+ , -- | Disallow spinners
82+ _cliConfig_noSpinner :: Bool
83+ , -- | Whether the last message was an Overwrite output
84+ _cliConfig_lock :: MVar Bool
85+ , -- | Whether the user tip (to make verbose) was already displayed
86+ _cliConfig_tipDisplayed :: IORef Bool
87+ , -- | Stack of logs from nested spinners
88+ _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString])
89+ , -- | Failure handler. How to log error and what exit status to use.
90+ _cliConfig_errorLogExitCode :: e -> (Text, Int)
91+ }
92+
45−class Monad m => HasCliConfig m where
93+class Monad m => HasCliConfig e m | m -> e where
46− getCliConfig :: m CliConfig
94+ getCliConfig :: m (CliConfig e)
95+
96+instance HasCliConfig e m => HasCliConfig e (ReaderT r m) where
97+ getCliConfig = lift getCliConfig
98+
99+instance (Monoid w, HasCliConfig e m) => HasCliConfig e (WriterT w m) where
100+ getCliConfig = lift getCliConfig
101+
102+instance HasCliConfig e m => HasCliConfig e (StateT s m) where
103+ getCliConfig = lift getCliConfig
104+
105+instance HasCliConfig e m => HasCliConfig e (ExceptT e m) where
106+ getCliConfig = lift getCliConfig
107+
108+--------------------------------------------------------------------------------
109+
110+newtype CliT e m a = CliT
111+ { unCliT :: ReaderT (CliConfig e) (DieT e m) a
112+ }
113+ deriving
114+ ( Functor, Applicative, Monad, MonadIO
115+ , MonadThrow, MonadCatch, MonadMask
116+ , MonadLog Output -- CliLog
117+ , MonadError e -- CliThrow
118+ , MonadReader (CliConfig e) -- HasCliConfig
119+ )
120+
121+instance MonadTrans (CliT e) where
122+ lift = CliT . lift . lift
47
123
48−instance Monad m => HasCliConfig (CliT m) where
124+instance Monad m => HasCliConfig e (CliT e m)where
49− getCliConfig = CliT ask
125+ getCliConfig = ask
lib/command/obelisk-command.cabal+1−3
@@ -15,7 +15,6 @@ library
15 , binary
15 , binary
16 , bytestring
16 , bytestring
17 , obelisk-cliapp
17 , obelisk-cliapp
18− , obelisk-migration
19 , containers
18 , containers
20 , data-default
19 , data-default
21 , directory
20 , directory
@@ -32,6 +31,7 @@ library
32 , modern-uri
31 , modern-uri
33 , monad-loops
32 , monad-loops
34 , mtl
33 , mtl
34+ , megaparsec
35 , network
35 , network
36 , network-uri
36 , network-uri
37 , optparse-applicative
37 , optparse-applicative
@@ -52,8 +52,6 @@ library
52 Obelisk.Command.Project
52 Obelisk.Command.Project
53 Obelisk.Command.Run
53 Obelisk.Command.Run
54 Obelisk.Command.Thunk
54 Obelisk.Command.Thunk
55− Obelisk.Command.Upgrade
56− Obelisk.Command.Upgrade.Hash
57 Obelisk.Command.Utils
55 Obelisk.Command.Utils
58 Obelisk.Command.VmBuilder
56 Obelisk.Command.VmBuilder
59 -- -fobject-code is so that the StaticPointers extension can work in ghci
57 -- -fobject-code is so that the StaticPointers extension can work in ghci
lib/command/src/Obelisk/App.hs+69−14
@@ -2,40 +2,90 @@
2{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE LambdaCase #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE StandaloneDeriving #-}
8{-# LANGUAGE StandaloneDeriving #-}
9+{-# LANGUAGE TemplateHaskell #-}
10+{-# LANGUAGE TupleSections #-}
8module Obelisk.App where
11module Obelisk.App where
9
12
13+import Control.Lens
10import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
14import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
11import Control.Monad.Reader (MonadIO, ReaderT (..), ask, runReaderT)
15import Control.Monad.Reader (MonadIO, ReaderT (..), ask, runReaderT)
12−import Control.Monad.Trans.Class (lift)
16+import Control.Monad.Writer (WriterT)
17+import Control.Monad.State (StateT)
18+import Control.Monad.Except (ExceptT, MonadError)
19+import Control.Monad.Trans.Class (MonadTrans, lift)
20+import Data.Text (Text)
13import System.Directory (XdgDirectory (XdgData), getXdgDirectory)
21import System.Directory (XdgDirectory (XdgData), getXdgDirectory)
14import Control.Monad.Log (MonadLog)
22import Control.Monad.Log (MonadLog)
15
23
16−import Obelisk.CliApp (CliConfig, CliT, HasCliConfig, getCliConfig, runCli, Output)
24+import Obelisk.CliApp
25+ ( CliConfig
26+ , CliLog
27+ , CliThrow
28+ , CliT (..)
29+ , ProcessFailure
30+ , AsProcessFailure (..)
31+ , AsUnstructuredError (..)
32+ , HasCliConfig
33+ , Output
34+ , runCli
35+ )
36+
37+data ObeliskError
38+ = ObeliskError_ProcessError
39+ { obeliskError_err :: ProcessFailure
40+ , obeliskError_mAnn :: Maybe Text
41+ }
42+ | ObeliskError_Unstructured Text
43+
44+makePrisms ''ObeliskError
45+
46+instance AsUnstructuredError ObeliskError where
47+ asUnstructuredError = _ObeliskError_Unstructured
48+
49+-- Only project when the other field is null, otherwise we are not law abiding.
50+instance AsProcessFailure ObeliskError where
51+ asProcessFailure = _ObeliskError_ProcessError . prism (, Nothing) f
52+ where f = \case
53+ (pf, Nothing) -> Right pf
54+ pann@(_, Just _) -> Left pann
17
55
18newtype Obelisk = Obelisk
56newtype Obelisk = Obelisk
19− { _obelisk_cliConfig :: CliConfig
57+ { _obelisk_cliConfig :: CliConfig ObeliskError
20 }
58 }
21
59
22newtype ObeliskT m a = ObeliskT
60newtype ObeliskT m a = ObeliskT
23− { unObeliskT :: ReaderT Obelisk (CliT m) a
61+ { unObeliskT :: ReaderT Obelisk (CliT ObeliskError m) a
24 }
62 }
25 deriving
63 deriving
26 ( Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask
64 ( Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask
65+ , MonadLog Output -- CliLog
66+ , MonadError ObeliskError -- CliThrow ObeliskError
27− , HasObelisk, HasCliConfig)
67+ , HasCliConfig ObeliskError)
28
68
29−deriving instance Monad m => MonadLog Output (ObeliskT m)
69+instance MonadTrans ObeliskT where
70+ lift = ObeliskT . lift . lift
30
71
31−class HasObelisk m where
72+class Monad m => HasObelisk m where
32 getObelisk :: m Obelisk
73 getObelisk :: m Obelisk
33
74
34−instance Monad m => HasObelisk (ReaderT Obelisk m) where
75+instance Monad m => HasObelisk (ObeliskT m) where
35− getObelisk = ask
76+ getObelisk = ObeliskT ask
77+
78+instance HasObelisk m => HasObelisk (ReaderT r m) where
79+ getObelisk = lift getObelisk
36
80
37−instance HasCliConfig m => HasCliConfig (ReaderT Obelisk m) where
81+instance (Monoid w, HasObelisk m) => HasObelisk (WriterT w m) where
38− getCliConfig = lift getCliConfig
82+ getObelisk = lift getObelisk
83+
84+instance HasObelisk m => HasObelisk (StateT r m) where
85+ getObelisk = lift getObelisk
86+
87+instance HasObelisk m => HasObelisk (ExceptT e m) where
88+ getObelisk = lift getObelisk
39
89
40runObelisk :: MonadIO m => Obelisk -> ObeliskT m a -> m a
90runObelisk :: MonadIO m => Obelisk -> ObeliskT m a -> m a
41runObelisk c =
91runObelisk c =
@@ -43,13 +93,18 @@ runObelisk c =
43 . flip runReaderT c
93 . flip runReaderT c
44 . unObeliskT
94 . unObeliskT
45
95
46−type MonadObelisk m =
96+type MonadInfallibleObelisk m =
47− ( MonadLog Output m
97+ ( CliLog m
48− , HasCliConfig m
98+ , HasCliConfig ObeliskError m
49 , HasObelisk m
99 , HasObelisk m
50 , MonadIO m
100 , MonadIO m
51 , MonadMask m
101 , MonadMask m
52 )
102 )
53
103
104+type MonadObelisk m =
105+ ( MonadInfallibleObelisk m
106+ , CliThrow ObeliskError m
107+ )
108+
54getObeliskUserStateDir :: IO FilePath
109getObeliskUserStateDir :: IO FilePath
55getObeliskUserStateDir = getXdgDirectory XdgData "obelisk"
110getObeliskUserStateDir = getXdgDirectory XdgData "obelisk"
lib/command/src/Obelisk/Command.hs+55−118
@@ -8,15 +8,13 @@
8module Obelisk.Command where
8module Obelisk.Command where
9
9
10import Control.Monad
10import Control.Monad
11−import Control.Monad.Catch (catch)
12import Control.Monad.IO.Class (liftIO)
11import Control.Monad.IO.Class (liftIO)
13import qualified Data.Binary as Binary
12import qualified Data.Binary as Binary
14import Data.Bool (bool)
13import Data.Bool (bool)
15import qualified Data.ByteString.Base16 as Base16
14import qualified Data.ByteString.Base16 as Base16
16import qualified Data.ByteString.Lazy as LBS
15import qualified Data.ByteString.Lazy as LBS
17import Data.List
16import Data.List
18−import Data.Maybe (catMaybes, listToMaybe)
17+import Data.Maybe (catMaybes)
19−import Data.Text (Text)
20import qualified Data.Text as T
18import qualified Data.Text as T
21import Data.Text.Encoding
19import Data.Text.Encoding
22import GHC.StaticPtr
20import GHC.StaticPtr
@@ -35,11 +33,8 @@ import Obelisk.Command.Deploy
35import Obelisk.Command.Project
33import Obelisk.Command.Project
36import Obelisk.Command.Run
34import Obelisk.Command.Run
37import Obelisk.Command.Thunk
35import Obelisk.Command.Thunk
38−import Obelisk.Command.Upgrade
39−import Obelisk.Command.Upgrade.Hash (getDirectoryHash, getHashAtGitRevision)
40import Obelisk.Command.Utils
36import Obelisk.Command.Utils
41import qualified Obelisk.Command.VmBuilder as VmBuilder
37import qualified Obelisk.Command.VmBuilder as VmBuilder
42−import Obelisk.Migration (Hash)
43
38
44
39
45data Args = Args
40data Args = Args
@@ -86,23 +81,21 @@ initSource = foldl1 (<|>)
86 , InitSource_Symlink <$> strOption (long "symlink" <> action "directory" <> metavar "PATH")
81 , InitSource_Symlink <$> strOption (long "symlink" <> action "directory" <> metavar "PATH")
87 ]
82 ]
88
83
84+initForce :: Parser Bool
85+initForce = switch (long "force" <> help "Allow ob init to overwrite files")
86+
89data ObCommand
87data ObCommand
90− = ObCommand_Init InitSource
88+ = ObCommand_Init InitSource Bool
91 | ObCommand_Deploy DeployCommand
89 | ObCommand_Deploy DeployCommand
92 | ObCommand_Run
90 | ObCommand_Run
93 | ObCommand_Thunk ThunkCommand
91 | ObCommand_Thunk ThunkCommand
94 | ObCommand_Repl
92 | ObCommand_Repl
95 | ObCommand_Watch
93 | ObCommand_Watch
96− | ObCommand_Upgrade (Maybe Text)
97 | ObCommand_Internal ObInternal
94 | ObCommand_Internal ObInternal
98 deriving Show
95 deriving Show
99
96
100data ObInternal
97data ObInternal
101 = ObInternal_RunStaticIO StaticKey
98 = ObInternal_RunStaticIO StaticKey
102− | ObInternal_Hash (Maybe Text)
103− | ObInternal_CreateMigration
104− | ObInternal_VerifyMigration
105− | ObInternal_Migrate Hash
106 | ObInternal_CLIDemo
99 | ObInternal_CLIDemo
107 deriving Show
100 deriving Show
108
101
@@ -127,13 +120,12 @@ inNixShell' p = withProjectRoot "." $ \root -> do
127obCommand :: ArgsConfig -> Parser ObCommand
120obCommand :: ArgsConfig -> Parser ObCommand
128obCommand cfg = hsubparser
121obCommand cfg = hsubparser
129 (mconcat
122 (mconcat
130− [ command "init" $ info (ObCommand_Init <$> initSource) $ progDesc "Initialize an Obelisk project"
123+ [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project"
131 , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project"
124 , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project"
132 , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode"
125 , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode"
133 , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories"
126 , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories"
134 , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter"
127 , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter"
135 , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings"
128 , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings"
136− , command "upgrade" $ info (ObCommand_Upgrade <$> argument (maybeReader $ Just . Just . T.pack) (action "branch" <> metavar "GITBRANCH" <> value Nothing <> help "Git branch of obelisk to update to (defaults to the thunk's branch)")) $ progDesc "Upgrade Obelisk in the project"
137 ])
129 ])
138 <|> subparser
130 <|> subparser
139 (mconcat
131 (mconcat
@@ -179,7 +171,6 @@ deployInitOpts = DeployInitOpts
179 <*> strOption (long "route" <> metavar "PUBLICROUTE" <> help "Publicly accessible URL of your app")
171 <*> strOption (long "route" <> metavar "PUBLICROUTE" <> help "Publicly accessible URL of your app")
180 <*> strOption (long "admin-email" <> metavar "ADMINEMAIL" <> help "Email address where administrative alerts will be sent")
172 <*> strOption (long "admin-email" <> metavar "ADMINEMAIL" <> help "Email address where administrative alerts will be sent")
181 <*> flag True False (long "disable-https" <> help "Disable automatic https configuration for the backend")
173 <*> flag True False (long "disable-https" <> help "Disable automatic https configuration for the backend")
182− <*> strOption (long "upstream" <> value "origin" <> metavar "REMOTE" <> help "git remote to use for the src thunk" <> showDefault)
183
174
184type TeamID = String
175type TeamID = String
185data PlatformDeployment = Android | IOS TeamID
176data PlatformDeployment = Android | IOS TeamID
@@ -202,17 +193,12 @@ data DeployInitOpts = DeployInitOpts
202 , _deployInitOpts_route :: String
193 , _deployInitOpts_route :: String
203 , _deployInitOpts_adminEmail :: String
194 , _deployInitOpts_adminEmail :: String
204 , _deployInitOpts_enableHttps :: Bool
195 , _deployInitOpts_enableHttps :: Bool
205− , _deployInitOpts_remote :: String
206 }
196 }
207 deriving Show
197 deriving Show
208
198
209internalCommand :: Parser ObInternal
199internalCommand :: Parser ObInternal
210internalCommand = subparser $ mconcat
200internalCommand = subparser $ mconcat
211 [ command "run-static-io" $ info (ObInternal_RunStaticIO <$> argument (eitherReader decodeStaticKey) (action "static-key")) mempty
201 [ command "run-static-io" $ info (ObInternal_RunStaticIO <$> argument (eitherReader decodeStaticKey) (action "static-key")) mempty
212− , command "hash" $ info (ObInternal_Hash <$> argument (maybeReader $ Just . Just . T.pack) (action "rev" <> metavar "GITREVISION" <> value Nothing <> help "Calculate for the specified git revision (defaults to working copy")) $ progDesc "Computes hash of working directory (or git revision)"
213− , command "create-migration" $ info (pure ObInternal_CreateMigration) mempty
214− , command "verify-migration" $ info (pure ObInternal_VerifyMigration) mempty
215− , command "migrate" $ info (ObInternal_Migrate <$> strArgument (action "fromHash" <> metavar "FROMHASH" <> help "Migrate from this hash")) $ progDesc "Perform a migrate from the given hash to HEAD of obelisk thunk"
216 , command "clidemo" $ info (pure ObInternal_CLIDemo) mempty
202 , command "clidemo" $ info (pure ObInternal_CLIDemo) mempty
217 ]
203 ]
218
204
@@ -224,32 +210,17 @@ thunkDirectoryParser = fmap (dropTrailingPathSeparator . normalise) . strArgumen
224 , help "Path to directory containing thunk data"
210 , help "Path to directory containing thunk data"
225 ]
211 ]
226
212
227−data ThunkPackOpts = ThunkPackOpts
228− { _thunkPackOpts_directory :: FilePath
229− , _thunkPackOpts_upstream :: String
230− }
231− deriving Show
232−
233data ThunkCommand
213data ThunkCommand
234 = ThunkCommand_Update [FilePath]
214 = ThunkCommand_Update [FilePath]
235 | ThunkCommand_Unpack [FilePath]
215 | ThunkCommand_Unpack [FilePath]
236− | ThunkCommand_Pack [ThunkPackOpts]
216+ | ThunkCommand_Pack [FilePath]
237 deriving Show
217 deriving Show
238
218
239−thunkPackOpts :: Parser ThunkPackOpts
240−thunkPackOpts = (ThunkPackOpts <$> thunkDirectoryParser <*>) . strOption $ mconcat
241− [ long "upstream"
242− , value "origin"
243− , metavar "REMOTE"
244− , help "Git remote that packed thunk will point to"
245− , showDefault
246− ]
247−
248thunkCommand :: Parser ThunkCommand
219thunkCommand :: Parser ThunkCommand
249thunkCommand = hsubparser $ mconcat
220thunkCommand = hsubparser $ mconcat
250 [ command "update" $ info (ThunkCommand_Update <$> some thunkDirectoryParser) $ progDesc "Update thunk to latest revision available"
221 [ command "update" $ info (ThunkCommand_Update <$> some thunkDirectoryParser) $ progDesc "Update thunk to latest revision available"
251 , command "unpack" $ info (ThunkCommand_Unpack <$> some thunkDirectoryParser) $ progDesc "Unpack thunk into git checkout of revision it points to"
222 , command "unpack" $ info (ThunkCommand_Unpack <$> some thunkDirectoryParser) $ progDesc "Unpack thunk into git checkout of revision it points to"
252− , command "pack" $ info (ThunkCommand_Pack <$> some thunkPackOpts) $ progDesc "Pack git checkout into thunk that points at given upstream"
223+ , command "pack" $ info (ThunkCommand_Pack <$> some thunkDirectoryParser) $ progDesc "Pack git checkout into thunk that points at the current branch's upstream"
253 ]
224 ]
254
225
255parserPrefs :: ParserPrefs
226parserPrefs :: ParserPrefs
@@ -267,7 +238,22 @@ mkObeliskConfig = do
267 -- This function should not use argument parser (full argument parsing happens post handoff)
238 -- This function should not use argument parser (full argument parsing happens post handoff)
268 let logLevel = toLogLevel $ "-v" `elem` cliArgs
239 let logLevel = toLogLevel $ "-v" `elem` cliArgs
269 notInteractive <- not <$> isInteractiveTerm
240 notInteractive <- not <$> isInteractiveTerm
241+-- instance MonadIO m => MonadError (Either Text ProcessFailed) (DieT m) where
242+-- throwError = \case
243+-- Left s -> do
244+-- putLog Alert s
245+-- liftIO $ exitWith $ ExitFailure 2
246+-- Right (ProcessFailed p code) -> do
247+-- putLog Alert $ "Process exited with code " <> T.pack (show code) <> "; " <> T.pack (show p)
248+-- liftIO $ exitWith $ ExitFailure 2
270− cliConf <- newCliConfig logLevel notInteractive notInteractive
249+ cliConf <- newCliConfig logLevel notInteractive notInteractive $ \case
250+ ObeliskError_ProcessError (ProcessFailure p code) ann ->
251+ ( "Process exited with code " <> T.pack (show code) <> "; " <> reconstructCommand p
252+ <> maybe "" ("\n\n" <>) ann
253+ , 2
254+ )
255+ ObeliskError_Unstructured msg -> (msg, 2)
256+
271 return $ Obelisk cliConf
257 return $ Obelisk cliConf
272 where
258 where
273 toLogLevel = bool Notice Debug
259 toLogLevel = bool Notice Debug
@@ -305,71 +291,33 @@ main' argsCfg = do
305 , "logging-level=" <> show logLevel
291 , "logging-level=" <> show logLevel
306 ]
292 ]
307
293
308− (mainWithHandOff argsCfg <=< parseHandoff) =<< liftIO getArgs
294+ --TODO: We'd like to actually use the parser to determine whether to hand off,
309− `catch`
295+ --but in the case where this implementation of 'ob' doesn't support all
310− \(ProcessFailed p code) -> failWith $ "Process exited with code " <> tshow code <> "; " <> tshow p
296+ --arguments being passed along, this could fail. For now, we don't bother
311−
297+ --with optparse-applicative until we've done the handoff.
312−-- Type representing the result of a handoff calculation.
298+ let go as = do
313−data HandOff m
299+ args' <- liftIO $ handleParseResult (execParserPure parserPrefs (argsInfo argsCfg) as)
314− = HandOff_Yes FilePath [String] -- Handoff immediately to the given impl with the given args
315− | HandOff_Decide (m Bool) FilePath [String] -- Handoff only if the action returns True
316− | HandOff_No [String] -- Do not handoff, and continue with the given args
317−
318−mainWithHandOff :: MonadObelisk m => ArgsConfig -> HandOff m -> m ()
319−mainWithHandOff argsCfg = \case
320− HandOff_Yes impl as -> do
321− putLog Debug $ "Handing off to project obelisk " <> T.pack impl
322− liftIO $ executeFile impl False ("--no-handoff" : as) Nothing
323− HandOff_Decide f impl as -> do
324− withSpinner' "Deciding whether to handoff"
325− (Just $ bool
326− "Continuing in ambient obelisk without handing off"
327− "Handed off to project obelisk") f >>= \case
328− True -> mainWithHandOff argsCfg $ HandOff_Yes impl as
329− False -> mainWithHandOff argsCfg $ HandOff_No as
330− HandOff_No as -> do
331− putLog Debug $ "Not handing off"
332− args' <- liftIO $ parseCLIArgs argsCfg as
333− warnIfExtraneousFlag args'
334− ob $ _args_command args'
335− where
336− warnIfExtraneousFlag a = case _args_noHandOffPassed a of
300+ case _args_noHandOffPassed args' of
337− False -> return ()
301+ False -> return ()
338− True -> putLog Warning $
339− "Ignoring unexpected --no-handoff (should only be passed once and as the first argument)"
302+ True -> putLog Warning "--no-handoff should only be passed once and as the first argument; ignoring"
340−
303+ ob $ _args_command args'
341−-- Handoff logic
342−parseHandoff :: MonadObelisk m => [String] -> m (HandOff m)
343−parseHandoff as' = case hasNoHandoff as' of
344− (True, as) ->
345− pure $ HandOff_No as
346− (False, as) -> findProjectObeliskCommand "." >>= \case
304+ handoffAndGo as = findProjectObeliskCommand "." >>= \case
347− Nothing -> do
305+ Nothing -> go as -- If not in a project, just run ourselves
348− putLog Debug "Not in a project; no need to hand off"
349− pure $ HandOff_No as
350− Just impl -> case getSubCommand as of
306+ Just impl -> do
351− Just "upgrade" ->
307+ -- Invoke the real implementation, using --no-handoff to prevent infinite recursion
352− pure $ HandOff_Decide (decideHandOffToProjectOb ".") impl as
308+ putLog Debug $ "Handing off to " <> T.pack impl
353− _ ->
309+ liftIO $ executeFile impl False ("--no-handoff" : myArgs) Nothing
354− pure $ HandOff_Yes impl as
310+ case myArgs of
355− where
356− getSubCommand = listToMaybe . filter (not . isPrefixOf "-")
357− hasNoHandoff = \case
358− "--no-handoff" : xs ->
359− -- If we've been told not to hand off, don't hand off
311+ "--no-handoff" : as -> go as -- If we've been told not to hand off, don't hand off
360− (True, xs)
361− x:xs
362− -- Otherwise bash completion would always hand-off even if the user isn't trying to
312+ a:as -- Otherwise bash completion would always hand-off even if the user isn't trying to
363− | "--bash-completion" `isPrefixOf` x && "--no-handoff" `elem` xs ->
313+ | "--bash-completion" `isPrefixOf` a
364− (True, x:xs)
314+ && "--no-handoff" `elem` as -> go (a:as)
365− | otherwise ->
315+ | otherwise -> handoffAndGo (a:as)
366− (False, x:xs)
316+ as -> handoffAndGo as
367− xs ->
368− (False, xs)
369
317
370ob :: MonadObelisk m => ObCommand -> m ()
318ob :: MonadObelisk m => ObCommand -> m ()
371ob = \case
319ob = \case
372− ObCommand_Init source -> initProject source
320+ ObCommand_Init source force -> initProject source force
373 ObCommand_Deploy dc -> case dc of
321 ObCommand_Deploy dc -> case dc of
374 DeployCommand_Init deployOpts -> withProjectRoot "." $ \root -> do
322 DeployCommand_Init deployOpts -> withProjectRoot "." $ \root -> do
375 let deployDir = _deployInitOpts_outputDir deployOpts
323 let deployDir = _deployInitOpts_outputDir deployOpts
@@ -387,7 +335,7 @@ ob = \case
387 Right (ThunkData_Packed ptr) -> return ptr
335 Right (ThunkData_Packed ptr) -> return ptr
388 Right (ThunkData_Checkout (Just ptr)) -> return ptr
336 Right (ThunkData_Checkout (Just ptr)) -> return ptr
389 Right (ThunkData_Checkout Nothing) ->
337 Right (ThunkData_Checkout Nothing) ->
390− getThunkPtr' False root (T.pack $ _deployInitOpts_remote deployOpts)
338+ getThunkPtr' False root
391 let sshKeyPath = _deployInitOpts_sshKey deployOpts
339 let sshKeyPath = _deployInitOpts_sshKey deployOpts
392 hostname = _deployInitOpts_hostname deployOpts
340 hostname = _deployInitOpts_hostname deployOpts
393 route = _deployInitOpts_route deployOpts
341 route = _deployInitOpts_route deployOpts
@@ -395,9 +343,11 @@ ob = \case
395 enableHttps = _deployInitOpts_enableHttps deployOpts
343 enableHttps = _deployInitOpts_enableHttps deployOpts
396 deployInit thunkPtr (root </> "config") deployDir
344 deployInit thunkPtr (root </> "config") deployDir
397 sshKeyPath hostname route adminEmail enableHttps
345 sshKeyPath hostname route adminEmail enableHttps
398− DeployCommand_Push remoteBuilder -> deployPush "." $ case remoteBuilder of
346+ DeployCommand_Push remoteBuilder -> do
347+ deployPath <- liftIO $ canonicalizePath "."
348+ deployPush deployPath $ case remoteBuilder of
399− Nothing -> pure []
349+ Nothing -> pure []
400− Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
350+ Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
401 DeployCommand_Update -> deployUpdate "."
351 DeployCommand_Update -> deployUpdate "."
402 DeployCommand_Test Android -> deployMobile "android" []
352 DeployCommand_Test Android -> deployMobile "android" []
403 DeployCommand_Test (IOS teamID) -> deployMobile "ios" [teamID]
353 DeployCommand_Test (IOS teamID) -> deployMobile "ios" [teamID]
@@ -406,28 +356,15 @@ ob = \case
406 ObCommand_Thunk tc -> case tc of
356 ObCommand_Thunk tc -> case tc of
407 ThunkCommand_Update thunks -> mapM_ updateThunkToLatest thunks
357 ThunkCommand_Update thunks -> mapM_ updateThunkToLatest thunks
408 ThunkCommand_Unpack thunks -> mapM_ unpackThunk thunks
358 ThunkCommand_Unpack thunks -> mapM_ unpackThunk thunks
409− ThunkCommand_Pack thunks -> forM_ thunks $ \(ThunkPackOpts dir upstream) -> packThunk dir (T.pack upstream)
359+ ThunkCommand_Pack thunks -> forM_ thunks packThunk
410 ObCommand_Repl -> runRepl
360 ObCommand_Repl -> runRepl
411 ObCommand_Watch -> inNixShell' $ static runWatch
361 ObCommand_Watch -> inNixShell' $ static runWatch
412− ObCommand_Upgrade branch -> do
413− upgradeObelisk "." branch
414 ObCommand_Internal icmd -> case icmd of
362 ObCommand_Internal icmd -> case icmd of
415 ObInternal_RunStaticIO k -> liftIO (unsafeLookupStaticPtr @(ObeliskT IO ()) k) >>= \case
363 ObInternal_RunStaticIO k -> liftIO (unsafeLookupStaticPtr @(ObeliskT IO ()) k) >>= \case
416 Nothing -> failWith $ "ObInternal_RunStaticIO: no such StaticKey: " <> T.pack (show k)
364 Nothing -> failWith $ "ObInternal_RunStaticIO: no such StaticKey: " <> T.pack (show k)
417 Just p -> do
365 Just p -> do
418 c <- getObelisk
366 c <- getObelisk
419 liftIO $ runObelisk c $ deRefStaticPtr p
367 liftIO $ runObelisk c $ deRefStaticPtr p
420− ObInternal_Hash revM -> do
421− hash <- case revM of
422− Nothing -> getDirectoryHash [migrationDirName] "."
423− Just rev -> fmap head $ getHashAtGitRevision [rev] [migrationDirName] "."
424− putLog Notice hash
425− ObInternal_CreateMigration ->
426− createMigrationEdgeFromHEAD "."
427− ObInternal_VerifyMigration ->
428− verifyMigration "."
429− ObInternal_Migrate fromHash ->
430− migrateObelisk "." fromHash
431 ObInternal_CLIDemo -> cliDemo
368 ObInternal_CLIDemo -> cliDemo
432
369
433--TODO: Clean up all the magic strings throughout this codebase
370--TODO: Clean up all the magic strings throughout this codebase
lib/command/src/Obelisk/Command/Deploy.hs+1−1
@@ -113,7 +113,7 @@ deployPush deployPath getNixBuilders = do
113 checkGitCleanStatus srcPath True >>= \case
113 checkGitCleanStatus srcPath True >>= \case
114 True -> do
114 True -> do
115 result <- build
115 result <- build
116− packThunk srcPath "origin" -- get upstream
116+ packThunk srcPath
117 return result
117 return result
118 False -> do
118 False -> do
119 _ <- failWith $ T.pack $ "ob deploy push: ensure " <> srcPath <> " has no pending changes and latest is pushed upstream."
119 _ <- failWith $ T.pack $ "ob deploy push: ensure " <> srcPath <> " has no pending changes and latest is pushed upstream."
lib/command/src/Obelisk/Command/Nix.hs+36−19
@@ -1,8 +1,12 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE ConstraintKinds #-}
1{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE LambdaCase #-}
3{-# LANGUAGE MultiParamTypeClasses #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
4{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE Rank2Types #-}
5{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TypeApplications #-}
6module Obelisk.Command.Nix
10module Obelisk.Command.Nix
7 ( nixBuild
11 ( nixBuild
8 , withNixRemoteCheck
12 , withNixRemoteCheck
@@ -15,7 +19,9 @@ module Obelisk.Command.Nix
15 , strArg
19 , strArg
16 ) where
20 ) where
17
21
18−import Control.Monad.Catch (catch, throwM)
22+--import Control.Monad.Catch (catch, throwM)
23+import Control.Lens
24+import Control.Monad.Except (ExceptT, runExceptT, throwError)
19import Control.Monad.IO.Class (liftIO)
25import Control.Monad.IO.Class (liftIO)
20import Data.Bool (bool)
26import Data.Bool (bool)
21import Data.Default
27import Data.Default
@@ -26,7 +32,7 @@ import System.Directory
26import System.Environment
32import System.Environment
27import System.Process (proc)
33import System.Process (proc)
28
34
29−import Obelisk.App (MonadObelisk)
35+import Obelisk.App (MonadInfallibleObelisk, MonadObelisk, ObeliskError (..))
30import Obelisk.CliApp
36import Obelisk.CliApp
31
37
32-- | Where to put nix-build output
38-- | Where to put nix-build output
@@ -81,8 +87,11 @@ instance Default NixBuildConfig where
81
87
82nixBuild :: MonadObelisk m => NixBuildConfig -> m FilePath
88nixBuild :: MonadObelisk m => NixBuildConfig -> m FilePath
83nixBuild cfg = withSpinner' ("Running nix-build on " <> desc) (Just $ const $ "Built " <> desc) $ do
89nixBuild cfg = withSpinner' ("Running nix-build on " <> desc) (Just $ const $ "Built " <> desc) $ do
84− withNixRemoteCheck $ readProcessAndLogStderr Debug $ proc "nix-build" $ mconcat
90+ output <- withNixRemoteCheck $ readProcessAndLogStderr Debug $ proc "nix-build" $ mconcat
85 [[path], attrArg, args, outLink, buildersArg]
91 [[path], attrArg, args, outLink, buildersArg]
92+ -- Remove final newline that Nix appends
93+ Just (outPath, '\n') <- pure $ T.unsnoc output
94+ pure $ T.unpack outPath
86 where
95 where
87 path = _target_path $ _nixBuildConfig_target cfg
96 path = _target_path $ _nixBuildConfig_target cfg
88 attr = _target_attr $ _nixBuildConfig_target cfg
97 attr = _target_attr $ _nixBuildConfig_target cfg
@@ -103,19 +112,27 @@ nixBuild cfg = withSpinner' ("Running nix-build on " <> desc) (Just $ const $ "B
103-- tell the user what to do.
112-- tell the user what to do.
104--
113--
105-- Added: June, 2018. Consider removing this eventually.
114-- Added: June, 2018. Consider removing this eventually.
106−withNixRemoteCheck :: MonadObelisk m => m a -> m a
115+withNixRemoteCheck
116+ :: MonadObelisk m
117+ => (forall m'. MonadInfallibleObelisk m' => ExceptT ObeliskError m' a)
118+ -> m a
107−withNixRemoteCheck f = f `catch` \e@(ProcessFailed _ _) -> do
119+withNixRemoteCheck f = do
120+ status <- runExceptT f
121+ case status of
122+ Right a -> pure a
123+ Left e -> throwError =<< case matching asProcessFailure e of
124+ Left _ -> pure e
108− liftIO (lookupEnv "NIX_REMOTE") >>= \case
125+ Right pf -> liftIO (lookupEnv "NIX_REMOTE") >>= \case
109− Just _ -> throwM e
126+ Just _ -> pure e
110− Nothing -> liftIO (writable <$> getPermissions "/nix/var/nix/db") >>= \case
127+ Nothing -> liftIO (writable <$> getPermissions "/nix/var/nix/db") >>= \case
111− True -> throwM e
128+ True -> pure e
112− False -> do
129+ False -> pure $ ObeliskError_ProcessError pf $ Just $ T.unlines
113− putLog Error "!!! "
130+ [ "!!! "
114− putLog Error "!!! A nix command failed to run. You might need to set the NIX_REMOTE environment variable"
131+ , "!!! A nix command failed to run. You might need to set the NIX_REMOTE environment variable"
115− putLog Error "!!! to `daemon`. To do this, run the following before running obelisk:"
132+ , "!!! to `daemon`. To do this, run the following before running obelisk:"
116− putLog Error "!!! "
133+ , "!!! "
117− putLog Error "!!! export NIX_REMOTE=daemon"
134+ , "!!! export NIX_REMOTE=daemon"
118− putLog Error "!!! "
135+ , "!!! "
119− putLog Error "!!! For details, see https://github.com/NixOS/nixpkgs/issues/5713"
136+ , "!!! For details, see https://github.com/NixOS/nixpkgs/issues/5713"
120− putLog Error "!!! "
137+ , "!!! "
121− throwM e
138+ ]
lib/command/src/Obelisk/Command/Project.hs+7−4
@@ -48,7 +48,6 @@ obeliskSourceWithBranch branch = ThunkSource_GitHub $ GitHubSource
48 { _gitHubSource_owner = "obsidiansystems"
48 { _gitHubSource_owner = "obsidiansystems"
49 , _gitHubSource_repo = "obelisk"
49 , _gitHubSource_repo = "obelisk"
50 , _gitHubSource_branch = Just branch
50 , _gitHubSource_branch = Just branch
51− , _gitHubSource_private = False
52 }
51 }
53
52
54data InitSource
53data InitSource
@@ -66,10 +65,14 @@ toImplDir :: FilePath -> FilePath
66toImplDir p = toObeliskDir p </> "impl"
65toImplDir p = toObeliskDir p </> "impl"
67
66
68-- | Create a new project rooted in the current directory
67-- | Create a new project rooted in the current directory
69−initProject :: MonadObelisk m => InitSource -> m ()
68+initProject :: MonadObelisk m => InitSource -> Bool -> m ()
70−initProject source = withSystemTempDirectory "ob-init" $ \tmpDir -> do
69+initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
71 let implDir = toImplDir tmpDir
70 let implDir = toImplDir tmpDir
72 obDir = toObeliskDir tmpDir
71 obDir = toObeliskDir tmpDir
72+ liftIO (listDirectory ".") >>= \case
73+ [] -> pure ()
74+ _ | force -> putLog Warning "Initializing in non-empty directory"
75+ | otherwise -> failWith "ob init requires an empty directory. Use the flag --force to init anyway, potentially overwriting files."
73 skeleton <- withSpinner "Setting up obelisk" $ do
76 skeleton <- withSpinner "Setting up obelisk" $ do
74 liftIO $ createDirectory obDir
77 liftIO $ createDirectory obDir
75 case source of
78 case source of
@@ -110,7 +113,7 @@ initProject source = withSystemTempDirectory "ob-init" $ \tmpDir -> do
110--TODO: Allow the user to ignore our security concerns
113--TODO: Allow the user to ignore our security concerns
111-- | Find the Obelisk implementation for the project at the given path
114-- | Find the Obelisk implementation for the project at the given path
112findProjectObeliskCommand :: MonadObelisk m => FilePath -> m (Maybe FilePath)
115findProjectObeliskCommand :: MonadObelisk m => FilePath -> m (Maybe FilePath)
113−findProjectObeliskCommand target = withSpinnerNoTrail "Locating project obelisk" $ do
116+findProjectObeliskCommand target = do
114 myUid <- liftIO getRealUserID
117 myUid <- liftIO getRealUserID
115 targetStat <- liftIO $ getFileStatus target
118 targetStat <- liftIO $ getFileStatus target
116 (result, insecurePaths) <- flip runStateT [] $ walkToProjectRoot target targetStat myUid >>= \case
119 (result, insecurePaths) <- flip runStateT [] $ walkToProjectRoot target targetStat myUid >>= \case
lib/command/src/Obelisk/Command/Run.hs+3−3
@@ -51,7 +51,7 @@ run = do
51 pkgs <- getLocalPkgs
51 pkgs <- getLocalPkgs
52 withGhciScript pkgs $ \dotGhciPath -> do
52 withGhciScript pkgs $ \dotGhciPath -> do
53 freePort <- getFreePort
53 freePort <- getFreePort
54− runGhcid dotGhciPath $ Just $ unwords ["run", show freePort, "Backend.backend", "Frontend.frontend"]
54+ runGhcid dotGhciPath $ Just $ unwords ["run", show freePort, "(runServeAsset Obelisk.Generated.Static.staticRootPath)", "Backend.backend", "Frontend.frontend"]
55
55
56runRepl :: MonadObelisk m => m ()
56runRepl :: MonadObelisk m => m ()
57runRepl = do
57runRepl = do
@@ -112,7 +112,7 @@ parseCabalPackage dir = do
112 mapM_ (putLog Error) $ fmap (T.pack . show) errors
112 mapM_ (putLog Error) $ fmap (T.pack . show) errors
113 return Nothing
113 return Nothing
114
114
115−withUTF8FileContentsM :: (MonadIO m, HasCliConfig m) => FilePath -> (ByteString -> CliT IO a) -> m a
115+withUTF8FileContentsM :: (MonadIO m, HasCliConfig e m) => FilePath -> (ByteString -> CliT e IO a) -> m a
116withUTF8FileContentsM fp f = do
116withUTF8FileContentsM fp f = do
117 c <- getCliConfig
117 c <- getCliConfig
118 liftIO $ withUTF8FileContents fp $ runCli c . f . toUTF8BS
118 liftIO $ withUTF8FileContents fp $ runCli c . f . toUTF8BS
@@ -173,7 +173,7 @@ runGhcid dotGhci mcmd = callCommand $ unwords $ "ghcid" : opts
173 opts =
173 opts =
174 [ "-W"
174 [ "-W"
175 --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user
175 --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user
176− , "--command='ghci -Wall -fwarn-redundant-constraints -ghci-script " <> dotGhci <> "' "
176+ , "--command='ghci -Wall -ignore-dot-ghci -fwarn-redundant-constraints -ghci-script " <> dotGhci <> "' "
177 , "--reload=config"
177 , "--reload=config"
178 , "--outputfile=ghcid-output.txt"
178 , "--outputfile=ghcid-output.txt"
179 ] <> testCmd
179 ] <> testCmd
lib/command/src/Obelisk/Command/Thunk.hs+345−265
@@ -27,10 +27,13 @@ module Obelisk.Command.Thunk
27 , updateThunk
27 , updateThunk
28 , getThunkPtr
28 , getThunkPtr
29 , getThunkPtr'
29 , getThunkPtr'
30+ , parseGitUri
31+ , uriThunkPtr
30 ) where
32 ) where
31
33
32import Control.Applicative
34import Control.Applicative
33−import Control.Exception (displayException, throwIO, try)
35+import Control.Exception (displayException, try)
36+import Control.Lens.Indexed hiding ((<.>))
34import Control.Monad
37import Control.Monad
35import Control.Monad.Catch (handle)
38import Control.Monad.Catch (handle)
36import Control.Monad.Except
39import Control.Monad.Except
@@ -39,15 +42,14 @@ import qualified Data.Aeson as Aeson
39import Data.Aeson.Encode.Pretty
42import Data.Aeson.Encode.Pretty
40import qualified Data.Aeson.Types as Aeson
43import qualified Data.Aeson.Types as Aeson
41import qualified Data.ByteString.Lazy as LBS
44import qualified Data.ByteString.Lazy as LBS
42−import qualified Data.ByteString.Lazy.Char8 as LBSC
43import Data.Default
45import Data.Default
44−import Data.Either.Combinators (rightToMaybe)
46+import Data.Either.Combinators (fromRight', rightToMaybe)
45−import Data.Foldable
46import Data.Git.Ref (Ref)
47import Data.Git.Ref (Ref)
47import qualified Data.Git.Ref as Ref
48import qualified Data.Git.Ref as Ref
48import qualified Data.List as L
49import qualified Data.List as L
49import Data.List.NonEmpty (NonEmpty (..))
50import Data.List.NonEmpty (NonEmpty (..))
50import qualified Data.List.NonEmpty as NonEmpty
51import qualified Data.List.NonEmpty as NonEmpty
52+import Data.Map (Map)
51import qualified Data.Map as Map
53import qualified Data.Map as Map
52import Data.Maybe
54import Data.Maybe
53import Data.Set (Set)
55import Data.Set (Set)
@@ -57,12 +59,9 @@ import qualified Data.Text as T
57import Data.Text.Encoding
59import Data.Text.Encoding
58import qualified Data.Text.IO as T
60import qualified Data.Text.IO as T
59import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
61import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
60−import Data.Yaml (parseMaybe, (.:))
62+import Data.Yaml (parseMaybe)
61−import qualified Data.Yaml as Yaml
62import GitHub
63import GitHub
63import GitHub.Data.Name
64import GitHub.Data.Name
64−import GitHub.Endpoints.Repos.Contents (archiveForR)
65−import Network.URI
66import Obelisk.Command.Nix
65import Obelisk.Command.Nix
67import System.Directory
66import System.Directory
68import System.FilePath
67import System.FilePath
@@ -70,6 +69,7 @@ import System.IO.Error
70import System.IO.Temp
69import System.IO.Temp
71import System.Posix (getSymbolicLinkStatus, modificationTime)
70import System.Posix (getSymbolicLinkStatus, modificationTime)
72import System.Process (proc)
71import System.Process (proc)
72+import Text.URI
73
73
74import Obelisk.App (MonadObelisk)
74import Obelisk.App (MonadObelisk)
75import Obelisk.CliApp
75import Obelisk.CliApp
@@ -112,97 +112,73 @@ data GitHubSource = GitHubSource
112 { _gitHubSource_owner :: Name Owner
112 { _gitHubSource_owner :: Name Owner
113 , _gitHubSource_repo :: Name Repo
113 , _gitHubSource_repo :: Name Repo
114 , _gitHubSource_branch :: Maybe (Name Branch)
114 , _gitHubSource_branch :: Maybe (Name Branch)
115− , _gitHubSource_private :: Bool
116 }
115 }
117 deriving (Show, Eq, Ord)
116 deriving (Show, Eq, Ord)
118
117
119data GitSource = GitSource
118data GitSource = GitSource
120 { _gitSource_url :: URI
119 { _gitSource_url :: URI
121− , _gitSource_branch :: Name Branch
120+ , _gitSource_branch :: Maybe (Name Branch)
122 , _gitSource_fetchSubmodules :: Bool
121 , _gitSource_fetchSubmodules :: Bool
123 }
122 }
124 deriving (Show, Eq, Ord)
123 deriving (Show, Eq, Ord)
125
124
125+-- | Convert a GitHub source to a regular Git source. Assumes no submodules.
126+forgetGithub :: Bool -> GitHubSource -> GitSource
127+forgetGithub useSsh s = GitSource
128+ { _gitSource_url = URI
129+ { uriScheme = Just $ fromRight' $ mkScheme $ if useSsh then "ssh" else "https"
130+ , uriAuthority = Right $ Authority
131+ { authUserInfo = UserInfo (fromRight' $ mkUsername "git") Nothing
132+ <$ guard useSsh
133+ , authHost = fromRight' $ mkHost "github.com"
134+ , authPort = Nothing
135+ }
136+ , uriPath = Just ( False
137+ , fromRight' . mkPathPiece <$>
138+ untagName (_gitHubSource_owner s)
139+ :| [ untagName (_gitHubSource_repo s) <> ".git" ]
140+ )
141+ , uriQuery = []
142+ , uriFragment = Nothing
143+ }
144+ , _gitSource_branch = _gitHubSource_branch s
145+ , _gitSource_fetchSubmodules = False
146+ }
147+
126getThunkGitBranch :: ThunkPtr -> Maybe Text
148getThunkGitBranch :: ThunkPtr -> Maybe Text
127getThunkGitBranch (ThunkPtr _ src) = fmap untagName $ case src of
149getThunkGitBranch (ThunkPtr _ src) = fmap untagName $ case src of
128 ThunkSource_GitHub s -> _gitHubSource_branch s
150 ThunkSource_GitHub s -> _gitHubSource_branch s
129− ThunkSource_Git s -> Just $ _gitSource_branch s
151+ ThunkSource_Git s -> _gitSource_branch s
130
152
131commitNameToRef :: Name Commit -> Ref
153commitNameToRef :: Name Commit -> Ref
132commitNameToRef (N c) = Ref.fromHex $ encodeUtf8 c
154commitNameToRef (N c) = Ref.fromHex $ encodeUtf8 c
133
155
134-- TODO: Use spinner here.
156-- TODO: Use spinner here.
135−getNixSha256ForUriUnpacked :: MonadObelisk m => URI -> m NixSha256
157+getNixSha256ForUriUnpacked
158+ :: MonadObelisk m
159+ => URI
160+ -> m NixSha256
136getNixSha256ForUriUnpacked uri =
161getNixSha256ForUriUnpacked uri =
137− withExitFailMessage ("nix-prefetch-url: Failed to determine sha256 hash of URL " <> T.pack (show uri)) $ do
162+ withExitFailMessage ("nix-prefetch-url: Failed to determine sha256 hash of URL " <> render uri) $ do
138− fmap T.pack $ withNixRemoteCheck $ readProcessAndLogStderr Debug $
163+ [hash] <- fmap T.lines $ withNixRemoteCheck $ readProcessAndLogOutput (Debug, Debug) $
139− proc "nix-prefetch-url" ["--unpack" , "--type" , "sha256" , show uri]
164+ proc "nix-prefetch-url" ["--unpack" , "--type" , "sha256" , T.unpack $ render uri]
165+ pure hash
140
166
141nixPrefetchGit :: MonadObelisk m => URI -> Text -> Bool -> m NixSha256
167nixPrefetchGit :: MonadObelisk m => URI -> Text -> Bool -> m NixSha256
142nixPrefetchGit uri rev fetchSubmodules =
168nixPrefetchGit uri rev fetchSubmodules =
143− withExitFailMessage ("nix-prefetch-git: Failed to determine sha256 hash of Git repo " <> T.pack (show uri) <> " at " <> rev) $ do
169+ withExitFailMessage ("nix-prefetch-git: Failed to determine sha256 hash of Git repo " <> render uri <> " at " <> rev) $ do
144 out <- withNixRemoteCheck $ readProcessAndLogStderr Debug $
170 out <- withNixRemoteCheck $ readProcessAndLogStderr Debug $
145 proc "nix-prefetch-git" $ filter (/="")
171 proc "nix-prefetch-git" $ filter (/="")
146− [ "--url", show uri
172+ [ "--url", T.unpack $ render uri
147 , "--rev", T.unpack rev
173 , "--rev", T.unpack rev
148 , if fetchSubmodules then "--fetch-submodules" else ""
174 , if fetchSubmodules then "--fetch-submodules" else ""
149 , "--quiet"
175 , "--quiet"
150 ]
176 ]
151
177
152− case parseMaybe (Aeson..: "sha256") =<< Aeson.decode (LBSC.pack out) of
178+ case parseMaybe (Aeson..: "sha256") =<< Aeson.decodeStrict (encodeUtf8 out) of
153− Nothing -> failWith $ "nix-prefetch-git: unrecognized output " <> T.pack out
179+ Nothing -> failWith $ "nix-prefetch-git: unrecognized output " <> out
154 Just x -> pure x
180 Just x -> pure x
155
181
156−getLatestGitRev :: MonadObelisk m => GitSource -> m ThunkRev
157−getLatestGitRev s = withSystemTempDirectory "git-clone" $ \tmpDir -> do
158− withExitFailMessage ("Unable to determine latest git revision for branch " <> branch) $ do
159− let git = fmap T.pack . readProcessAndLogStderr Debug . gitProc tmpDir
160− out <- liftA2 (<>)
161− (git ["clone", "--no-checkout", show $ _gitSource_url s]) -- Might be able to use --bare here instead to go faster.
162− (git ["log", T.unpack branch, "--max-count=1", "--format=%H"])
163−
164− case T.strip <$> safeLast (T.lines out) of
165− Nothing -> failWith $ "Unable to parse git log: " <> out
166− Just rev -> do
167− putLog Debug $ "Latest commit in " <> branch <> " is " <> rev
168− sha <- nixPrefetchGit (_gitSource_url s) rev (_gitSource_fetchSubmodules s)
169− putLog Debug $ "Nix sha256 is " <> sha
170− pure $ ThunkRev
171− { _thunkRev_commit = Ref.fromHexString $ T.unpack rev
172− , _thunkRev_nixSha256 = sha
173− }
174− where
175− remote :: Text = "origin"
176− branch :: Text = remote <> "/" <> untagName (_gitSource_branch s)
177−
178− safeLast [] = Nothing
179− safeLast xs = Just $ last xs
180−
181−-- | Get the latest revision available from the given source
182−getLatestRev :: MonadObelisk m => ThunkSource -> m ThunkRev
183−getLatestRev = \case
184− ThunkSource_GitHub s -> do
185− auth <- liftIO $ getHubAuth "github.com"
186− commitsResult <- liftIO $ executeRequestMaybe auth $ commitsWithOptionsForR
187− (_gitHubSource_owner s)
188− (_gitHubSource_repo s)
189− (FetchAtLeast 1)
190− $ case _gitHubSource_branch s of
191− Nothing -> []
192− Just b -> [CommitQuerySha $ untagName b]
193− commitInfos <- either (liftIO . throwIO) return commitsResult
194− commitInfo : _ <- return $ toList commitInfos
195− let commit = commitSha commitInfo
196− putLog Debug $ "Latest commit is " <> untagName commit
197− Right archiveUri <- liftIO $ executeRequestMaybe auth $ archiveForR (_gitHubSource_owner s) (_gitHubSource_repo s) ArchiveFormatTarball $ Just $ untagName commit
198− nixSha256 <- getNixSha256ForUriUnpacked archiveUri
199− putLog Debug $ "Nix sha256 is " <> nixSha256
200− return $ ThunkRev
201− { _thunkRev_commit = commitNameToRef commit
202− , _thunkRev_nixSha256 = nixSha256
203− }
204− ThunkSource_Git s -> getLatestGitRev s
205−
206--TODO: Pretty print these
182--TODO: Pretty print these
207data ReadThunkError
183data ReadThunkError
208 = ReadThunkError_UnrecognizedFiles
184 = ReadThunkError_UnrecognizedFiles
@@ -311,18 +287,16 @@ parseGitHubSource v = do
311 owner <- v Aeson..: "owner"
287 owner <- v Aeson..: "owner"
312 repo <- v Aeson..: "repo"
288 repo <- v Aeson..: "repo"
313 branch <- v Aeson..:! "branch"
289 branch <- v Aeson..:! "branch"
314− mPrivate <- v Aeson..:! "private"
315 pure $ GitHubSource
290 pure $ GitHubSource
316 { _gitHubSource_owner = owner
291 { _gitHubSource_owner = owner
317 , _gitHubSource_repo = repo
292 , _gitHubSource_repo = repo
318 , _gitHubSource_branch = branch
293 , _gitHubSource_branch = branch
319− , _gitHubSource_private = fromMaybe False mPrivate
320 }
294 }
321
295
322parseGitSource :: Aeson.Object -> Aeson.Parser GitSource
296parseGitSource :: Aeson.Object -> Aeson.Parser GitSource
323parseGitSource v = do
297parseGitSource v = do
324 Just url <- parseGitUri <$> v Aeson..: "url"
298 Just url <- parseGitUri <$> v Aeson..: "url"
325− branch <- v Aeson..: "branch"
299+ branch <- v Aeson..:! "branch"
326 fetchSubmodules <- v Aeson..:! "fetchSubmodules"
300 fetchSubmodules <- v Aeson..:! "fetchSubmodules"
327 pure $ GitSource
301 pure $ GitSource
328 { _gitSource_url = url
302 { _gitSource_url = url
@@ -351,20 +325,14 @@ encodeThunkPtrData (ThunkPtr rev src) = case src of
351 ThunkSource_GitHub s -> encodePretty' githubCfg $ Aeson.object $ catMaybes
325 ThunkSource_GitHub s -> encodePretty' githubCfg $ Aeson.object $ catMaybes
352 [ Just $ "owner" .= _gitHubSource_owner s
326 [ Just $ "owner" .= _gitHubSource_owner s
353 , Just $ "repo" .= _gitHubSource_repo s
327 , Just $ "repo" .= _gitHubSource_repo s
354− , case _gitHubSource_branch s of
328+ , ("branch" .=) <$> _gitHubSource_branch s
355− Just b -> Just $ "branch" .= b
356− Nothing -> Nothing
357− , if _gitHubSource_private s
358− then Just $ "private" .= True
359− else Nothing
360 , Just $ "rev" .= Ref.toHexString (_thunkRev_commit rev)
329 , Just $ "rev" .= Ref.toHexString (_thunkRev_commit rev)
361 , Just $ "sha256" .= _thunkRev_nixSha256 rev
330 , Just $ "sha256" .= _thunkRev_nixSha256 rev
362 ]
331 ]
363 ThunkSource_Git s -> encodePretty' plainGitCfg $ Aeson.object $ catMaybes
332 ThunkSource_Git s -> encodePretty' plainGitCfg $ Aeson.object $ catMaybes
364− --TODO: Is this safe? read/show is jank for `URI`
365− [ Just $ "url" .= show (_gitSource_url s)
333+ [ Just $ "url" .= render (_gitSource_url s)
366 , Just $ "rev" .= Ref.toHexString (_thunkRev_commit rev)
334 , Just $ "rev" .= Ref.toHexString (_thunkRev_commit rev)
367− , Just $ "branch" .= _gitSource_branch s
335+ , ("branch" .=) <$> _gitSource_branch s
368 , Just $ "sha256" .= _thunkRev_nixSha256 rev
336 , Just $ "sha256" .= _thunkRev_nixSha256 rev
369 , Just $ "fetchSubmodules" .= _gitSource_fetchSubmodules s
337 , Just $ "fetchSubmodules" .= _gitSource_fetchSubmodules s
370 ]
338 ]
@@ -496,20 +464,6 @@ plainGitStandaloneLoaderV2 = T.unlines
496thunkFileNames :: [FilePath]
464thunkFileNames :: [FilePath]
497thunkFileNames = ["default.nix", "github.json", "git.json"]
465thunkFileNames = ["default.nix", "github.json", "git.json"]
498
466
499−-- | Look for authentication info from the 'hub' command
500−getHubAuth
501− :: Text -- ^ Domain name
502− -> IO (Maybe Auth)
503−getHubAuth domain = do
504− hubConfig <- getXdgDirectory XdgConfig "hub"
505− Yaml.decodeFileEither hubConfig >>= \case
506− Left _ -> return Nothing
507− Right v -> return $ flip parseMaybe v $ \v' -> do
508− Yaml.Array domainConfigs <- v' .: domain --TODO: Determine what multiple domainConfigs means
509− [Yaml.Object domainConfig] <- return $ toList domainConfigs
510− token <- domainConfig .: "oauth_token"
511− return $ OAuth $ encodeUtf8 token
512−
513--TODO: when checking something out, make a shallow clone
467--TODO: when checking something out, make a shallow clone
514
468
515-- | Checks a cache directory to see if there is a fresh symlink
469-- | Checks a cache directory to see if there is a fresh symlink
@@ -588,7 +542,7 @@ updateThunk p f = withSystemTempDirectory "obelisk-thunkptr-" $ \tmpDir -> do
588 p' <- copyThunkToTmp tmpDir p
542 p' <- copyThunkToTmp tmpDir p
589 unpackThunk' True p'
543 unpackThunk' True p'
590 result <- f p'
544 result <- f p'
591− updateThunkFromTmp p' "origin" -- TODO: stop hardcoding remote
545+ updateThunkFromTmp p'
592 return result
546 return result
593 where
547 where
594 copyThunkToTmp tmpDir thunkDir = readThunk thunkDir >>= \case
548 copyThunkToTmp tmpDir thunkDir = readThunk thunkDir >>= \case
@@ -599,8 +553,8 @@ updateThunk p f = withSystemTempDirectory "obelisk-thunkptr-" $ \tmpDir -> do
599 proc "cp" ["-r", "-T", thunkDir, tmpThunk]
553 proc "cp" ["-r", "-T", thunkDir, tmpThunk]
600 return tmpThunk
554 return tmpThunk
601 Right _ -> failWith $ "Thunk is not packed"
555 Right _ -> failWith $ "Thunk is not packed"
602− updateThunkFromTmp p' remote = do
556+ updateThunkFromTmp p' = do
603− packThunk' True p' remote
557+ packThunk' True p'
604 callProcessAndLogOutput (Notice, Error) $
558 callProcessAndLogOutput (Notice, Error) $
605 proc "cp" ["-r", "-T", p', p]
559 proc "cp" ["-r", "-T", p', p]
606
560
@@ -619,190 +573,316 @@ unpackThunk' noTrail thunkDir = readThunk thunkDir >>= \case
619 let (thunkParent, thunkName) = splitFileName thunkDir
573 let (thunkParent, thunkName) = splitFileName thunkDir
620 withTempDirectory thunkParent thunkName $ \tmpRepo -> do
574 withTempDirectory thunkParent thunkName $ \tmpRepo -> do
621 let obGitDir = tmpRepo </> ".git" </> "obelisk"
575 let obGitDir = tmpRepo </> ".git" </> "obelisk"
622− case _thunkPtr_source tptr of
576+ s = case _thunkPtr_source tptr of
623− ThunkSource_GitHub s -> do
577+ ThunkSource_GitHub s' -> forgetGithub False s'
624− mauth <- liftIO $ getHubAuth "github.com"
625− repoResult <- liftIO $ executeRequestMaybe mauth $ repositoryR (_gitHubSource_owner s) (_gitHubSource_repo s)
626− githubURI <- either (liftIO . throwIO) (return . repoSshUrl) repoResult >>= \case
627− Nothing -> failWith "Cannot determine clone URI for thunk source"
628− Just c -> return $ T.unpack $ getUrl c
629−
630− withSpinner' ("Fetching thunk " <> T.pack thunkDir)
631− ( finalMsg noTrail $ const $ "Fetched thunk " <> T.pack thunkDir) $ do
632− callProcessAndLogOutput (Debug, Debug) $
633− proc "hub" ["clone", "-n", githubURI, tmpRepo]
634− --If this directory already exists then something is weird and we should fail
635− liftIO $ createDirectory obGitDir
636− callProcessAndLogOutput (Debug, Error) $
637− proc "cp" ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
638−
639− -- Checkout
640− putLog Debug $ "Checking out " <> T.pack (show $ maybe "" untagName $ _gitHubSource_branch s)
641− callProcessAndLogOutput (Debug, Debug) $
642− proc "hub" $ concat
643− [ ["-C", tmpRepo]
644− , pure "checkout"
645− , maybe [] (\n -> ["-B", T.unpack (untagName n)]) (_gitHubSource_branch s)
646− , pure $ Ref.toHexString $ _thunkRev_commit $ _thunkPtr_rev tptr
647− ]
648− -- Set upstream branch
649− forM_ (_gitHubSource_branch s) $ \branch ->
650− callProcessAndLogOutput (Debug, Error) $
651− proc "hub" ["-C", tmpRepo, "branch", "-u", "origin/" <> T.unpack (untagName branch)]
652− callProcessAndLogOutput (Notice, Error) $ proc "rm" ["-r", thunkDir]
653− callProcessAndLogOutput (Notice, Error) $ proc "mv" ["-T", tmpRepo, thunkDir]
654−
655− ThunkSource_Git s -> do
578+ ThunkSource_Git s' -> s'
656− withSpinner' ("Fetching thunk " <> T.pack thunkName)
579+ withSpinner' ("Fetching thunk " <> T.pack thunkName)
657− (finalMsg noTrail $ const $ "Fetched thunk " <> T.pack thunkName) $ do
580+ (finalMsg noTrail $ const $ "Fetched thunk " <> T.pack thunkName) $ do
658− let git = callProcessAndLogOutput (Notice, Notice) . gitProc tmpRepo
581+ let git = callProcessAndLogOutput (Notice, Notice) . gitProc tmpRepo
582+ git $ [ "clone" ]
659− git ["clone", if _gitSource_fetchSubmodules s then "--recursive" else "", show (_gitSource_url s)]
583+ ++ ("--recursive" <$ guard (_gitSource_fetchSubmodules s))
584+ ++ [ T.unpack $ render $ _gitSource_url s ]
660− git ["reset", "--hard", Ref.toHexString $ _thunkRev_commit $ _thunkPtr_rev tptr]
585+ git ["reset", "--hard", Ref.toHexString $ _thunkRev_commit $ _thunkPtr_rev tptr]
661− when (_gitSource_fetchSubmodules s) $
586+ when (_gitSource_fetchSubmodules s) $
662− git ["submodule", "update", "--recursive", "--init"]
587+ git ["submodule", "update", "--recursive", "--init"]
663− git ["branch", "-u", "origin/" <> T.unpack (untagName $ _gitSource_branch s)]
588+ case _gitSource_branch s of
664−
589+ Just b -> git ["branch", "-u", "origin/" <> T.unpack (untagName $ b)]
590+ Nothing -> pure ()
591+
665− liftIO $ createDirectory obGitDir
592+ liftIO $ createDirectory obGitDir
666− callProcessAndLogOutput (Notice, Error) $
593+ callProcessAndLogOutput (Notice, Error) $
667− proc "cp" ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
594+ proc "cp" ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
668− callProcessAndLogOutput (Notice, Error) $
595+ callProcessAndLogOutput (Notice, Error) $
669− proc "rm" ["-r", thunkDir]
596+ proc "rm" ["-r", thunkDir]
670− callProcessAndLogOutput (Notice, Error) $
597+ callProcessAndLogOutput (Notice, Error) $
671− proc "mv" ["-T", tmpRepo, thunkDir]
598+ proc "mv" ["-T", tmpRepo, thunkDir]
672
599
673--TODO: add force mode to pack even if changes are present
600--TODO: add force mode to pack even if changes are present
674--TODO: add a rollback mode to pack to the original thunk
601--TODO: add a rollback mode to pack to the original thunk
675−packThunk :: MonadObelisk m => FilePath -> Text -> m ()
602+packThunk :: MonadObelisk m => FilePath -> m ()
676packThunk = packThunk' False
603packThunk = packThunk' False
677
604
678−packThunk' :: MonadObelisk m => Bool -> FilePath -> Text -> m ()
605+packThunk' :: MonadObelisk m => Bool -> FilePath -> m ()
679−packThunk' noTrail thunkDir upstream = readThunk thunkDir >>= \case
606+packThunk' noTrail thunkDir = readThunk thunkDir >>= \case
680 Left err -> failWith $ T.pack $ "thunk pack: " <> show err
607 Left err -> failWith $ T.pack $ "thunk pack: " <> show err
681 Right (ThunkData_Packed _) -> failWith "pack: thunk is already packed"
608 Right (ThunkData_Packed _) -> failWith "pack: thunk is already packed"
682 Right (ThunkData_Checkout _) -> do
609 Right (ThunkData_Checkout _) -> do
683 withSpinner' ("Packing thunk " <> T.pack thunkDir)
610 withSpinner' ("Packing thunk " <> T.pack thunkDir)
684 (finalMsg noTrail $ const $ "Packed thunk " <> T.pack thunkDir) $ do
611 (finalMsg noTrail $ const $ "Packed thunk " <> T.pack thunkDir) $ do
685− thunkPtr <- getThunkPtr thunkDir upstream
612+ thunkPtr <- getThunkPtr thunkDir
686 callProcessAndLogOutput (Debug, Error) $ proc "rm" ["-rf", thunkDir]
613 callProcessAndLogOutput (Debug, Error) $ proc "rm" ["-rf", thunkDir]
687 liftIO $ createThunk thunkDir thunkPtr
614 liftIO $ createThunk thunkDir thunkPtr
688
615
689−getThunkPtr :: MonadObelisk m => FilePath -> Text -> m ThunkPtr
616+getThunkPtr :: MonadObelisk m => FilePath -> m ThunkPtr
690getThunkPtr = getThunkPtr' True
617getThunkPtr = getThunkPtr' True
691
618
692−getThunkPtr' :: MonadObelisk m => Bool -> FilePath -> Text -> m ThunkPtr
619+getThunkPtr' :: forall m. MonadObelisk m => Bool -> FilePath -> m ThunkPtr
693−getThunkPtr' checkClean thunkDir upstream = do
620+getThunkPtr' checkClean thunkDir = do
694− when checkClean $ ensureCleanGitRepo thunkDir True $
621+ when checkClean $ ensureCleanGitRepo thunkDir True $
695− "thunk pack: thunk checkout contains unsaved modifications"
622+ "thunk pack: thunk checkout contains unsaved modifications"
696−
623+
697− -- Check whether there are any stashes
624+ -- Check whether there are any stashes
698− stashOutput <- readGitProcess thunkDir ["stash", "list"]
625+ stashOutput <- readGitProcess thunkDir ["stash", "list"]
699− when checkClean $ case T.null stashOutput of
626+ when checkClean $ case T.null stashOutput of
700− False -> do
627+ False -> do
701− failWith $ T.unlines $
628+ failWith $ T.unlines $
702− [ "thunk pack: thunk checkout has stashes"
629+ [ "thunk pack: thunk checkout has stashes"
703− , "git stash list:"
630+ , "git stash list:"
704− ] ++ T.lines stashOutput
631+ ] ++ T.lines stashOutput
705− True -> return ()
632+ True -> return ()
706−
633+
707− --Check whether all local heads are pushed
634+ -- Get information on all branches and their (optional) designated upstream
635+ -- correspondents
708− repoHeads <- T.lines <$> readGitProcess thunkDir ["for-each-ref", "--format=%(refname:short)", "refs/heads/"]
636+ (headDump :: [Text]) <- T.lines <$> readGitProcess thunkDir
709− remotes <- T.lines <$> readGitProcess thunkDir ["remote"]
637+ [ "for-each-ref"
710− --Check that the upstream specified actually exists
638+ , "--format=%(refname:short) %(upstream:short) %(upstream:remotename)"
711− case L.find (== upstream) remotes of
639+ , "refs/heads/"
712− Nothing -> failWith $ "thunk pack: upstream " <> upstream <> " does not exist. Available upstreams are " <> T.intercalate ", " remotes
640+ ]
713− Just _ -> return ()
641+
714− -- iterate over cartesian product
642+ (headInfo :: Map Text (Maybe (Text, Text)))
643+ <- fmap Map.fromList $ forM headDump $ \line -> do
644+ (branch : restOfLine) <- pure $ T.words line
645+ mUpstream <- case restOfLine of
646+ [] -> pure Nothing
647+ [u, r] -> pure $ Just (u, r)
648+ (_:_) -> failWith "git for-each-ref invalid output"
649+ pure (branch, mUpstream)
650+
651+ putLog Debug $ "branches: " <> T.pack (show headInfo)
652+
653+ let errorMap :: Map Text ()
654+ headUpstream :: Map Text (Text, Text)
655+ (errorMap, headUpstream) = flip Map.mapEither headInfo $ \case
656+ Nothing -> Left ()
657+ Just b -> Right b
658+
659+ putLog Debug $ "branches with upstream branch set: " <> T.pack (show headUpstream)
660+
661+ -- Check that every branch has a remote equivalent
715− when checkClean $ forM_ repoHeads $ \hd -> do
662+ when checkClean $ do
716− [localRev] <- T.lines <$> readGitProcess thunkDir ["rev-parse", T.unpack hd]
663+ let untrackedBranches = Map.keys errorMap
717− forM_ remotes $ \rm -> do
664+ when (not $ L.null untrackedBranches) $ failWith $ T.unlines $
718− --TODO: The error you get if a branch isn't pushed anywhere could be made more user friendly
665+ [ "thunk pack: Certain branches in the thunk have no upstream branch \
719− [remoteMergeRev] <- fmap T.lines $ readGitProcess thunkDir
666+ \set. This means don't know to check whether all your work is \
720− ["merge-base", T.unpack localRev, "remotes/" <> T.unpack rm <> "/" <> T.unpack hd]
667+ \saved. The offending branches are:"
721− case remoteMergeRev == localRev of
668+ , ""
722− False -> failWith $ mconcat [ "thunk unpack: branch ", hd, " has not been pushed to ", rm ]
669+ , T.unwords untrackedBranches
723− True -> return ()
670+ , ""
724−
671+ , "To fix this, you probably want to do:"
672+ , ""
673+ ] ++
674+ ((\branch -> "git push -u origin " <> branch) <$> untrackedBranches) ++
675+ [ ""
676+ , "These will push the branches to the default remote under the same \
677+ \name, and (thanks to the `-u`) remember that choice so you don't \
678+ \get this error again."
679+ ]
680+
681+ -- loosely by https://stackoverflow.com/questions/7773939/show-git-ahead-and-behind-info-for-all-branches-including-remotes
682+ stats <- iforM headUpstream $ \branch (upstream, _remote) -> do
683+ (stat :: [Text]) <- T.lines <$> readGitProcess thunkDir
684+ [ "rev-list", "--left-right"
685+ , T.unpack branch <> "..." <> T.unpack upstream
686+ ]
687+ let ahead = length $ [ () | Just ('<', _) <- T.uncons <$> stat ]
688+ behind = length $ [ () | Just ('>', _) <- T.uncons <$> stat ]
689+ pure (upstream, (ahead, behind))
690+
691+ -- Those branches which have commits ahead of, i.e. not on, the upstream
692+ -- branch. Purely being behind is fine.
693+ let nonGood = Map.filter ((/= 0) . fst . snd) stats
694+
695+ when (not $ Map.null nonGood) $ failWith $ T.unlines $
696+ [ "thunk pack: Certain branches in the thunk have commits not yet pushed upstream:"
697+ , ""
698+ ] ++
699+ (flip map (Map.toList nonGood) $ \(branch, (upstream, (ahead, behind))) -> mconcat
700+ [" ", branch, " ahead: ", T.pack (show ahead), " behind: ", T.pack (show behind), " remote branch ", upstream]) ++
701+ [ ""
702+ , "Please push these upstream and try again. (Or just fetch, if they are somehow \
703+ \pushed but this repo's remote tracking branches don't know it.)"
704+ ]
705+
725− --We assume it's safe to pack the thunk at this point
706+ -- We assume it's safe to pack the thunk at this point
707+ putLog Informational $ "All changes safe in git remotes. OK to pack thunk."
708+
709+ -- Get current branch ``
710+ mCurrentBranch <- listToMaybe
711+ <$> T.lines
712+ <$> readGitProcess thunkDir ["rev-parse", "--abbrev-ref", "HEAD"]
713+
714+ let remote = maybe "origin" snd $ flip Map.lookup headUpstream =<< mCurrentBranch
715+
726− [remoteUri'] <- fmap T.lines $ readGitProcess thunkDir ["config", "--get", "remote." <> T.unpack upstream <> ".url"]
716+ [remoteUri'] <- fmap T.lines $ readGitProcess thunkDir
727−
717+ [ "config"
718+ , "--get"
719+ , "remote." <> T.unpack remote <> ".url"
720+ ]
721+
722+ remoteUri <- case parseGitUri remoteUri' of
728− let uriParseFailure = "Could not identify git remote: " <> remoteUri'
723+ Nothing -> failWith $ "Could not identify git remote: " <> remoteUri'
729− mRemoteUri = parseGitUri remoteUri'
724+ Just uri -> pure uri
730− case mRemoteUri of
725+ uriThunkPtr remoteUri mCurrentBranch
731− Nothing -> failWith uriParseFailure
726+
732− Just remoteUri -> do
727+-- | Get the latest revision available from the given source
733− refs <- fmap (map (refsToTuples . T.words) . T.lines) $ readGitProcess thunkDir ["show-ref", "--head"]
728+getLatestRev :: MonadObelisk m => ThunkSource -> m ThunkRev
734− -- safe because exactly one hash is associated with HEAD
729+getLatestRev os = do
735− let Just currentHead = fst <$> L.find ((== "HEAD") . snd) refs
730+ let gitS = case os of
736− remoteHeads = Map.fromList $ catMaybes $ fmap
731+ ThunkSource_GitHub s -> forgetGithub False s
737− (\(x,y) -> (,) x <$> T.stripPrefix ("refs/remotes/" <> upstream <> "/") y)
732+ ThunkSource_Git s -> s
738− refs
733+ (_, commit) <- gitGetCommitBranch (_gitSource_url gitS) (untagName <$> _gitSource_branch gitS)
739− localHeads = Map.fromList $ catMaybes $ fmap
734+ case os of
740− (\(x,y) -> (,) x <$> T.stripPrefix "refs/heads/" y)
735+ ThunkSource_GitHub s -> githubThunkRev s commit
741− refs
736+ ThunkSource_Git s -> gitThunkRev s commit
742− currentUpstreamBranch = Map.lookup currentHead remoteHeads
737+
743− <|> Map.lookup currentHead localHeads
738+-- | Convert a URI to a thunk
744− if isGithubThunk remoteUri
739+--
745− then githubThunkPtr remoteUri currentHead currentUpstreamBranch
740+-- If the URL is a github URL, we try to just download an archive for
746− else gitThunkPtr remoteUri currentHead currentUpstreamBranch
741+-- performance. If that doesn't work (e.g. authentication issue), we fall back
747− where
742+-- on just doing things the normal way for git repos in general, and save it as
748− refsToTuples [x, y] = (x, y)
743+-- a regular git thunk.
749− refsToTuples x = error $ "thunk pack: cannot parse ref " <> show x
744+uriThunkPtr :: MonadObelisk m => URI -> Maybe Text -> m ThunkPtr
750− isGithubThunk u
745+uriThunkPtr uri mbranch = do
751− | Just uriAuth <- uriAuthority u
746+ (_, commit) <- gitGetCommitBranch uri mbranch
752− = case uriScheme u of
747+ (src, rev) <- case uriToThunkSource uri mbranch of
753− "ssh:" -> uriAuth == URIAuth "git@" "github.com" ""
748+ ThunkSource_GitHub s -> do
754− s -> s `L.elem` [ "git:", "https:", "http:" ] -- "http:" just redirects to "https:"
749+ rev <- runExceptT $ githubThunkRev s commit
755− && uriRegName uriAuth == "github.com"
750+ case rev of
756− | otherwise = False
751+ Right r -> pure (ThunkSource_GitHub s, r)
757− githubThunkPtr u commit' branch' = do
752+ Left e -> do
758− ["/", owner', repo'] <- return $ splitDirectories (uriPath u)
753+ putLog Warning $ "\
759− let owner = N (T.pack owner')
754+\Failed to fetch archive from GitHub. This is probably a private repo. \
760− dropGitExtension r = fromMaybe r $ T.stripSuffix ".git" r
755+\Falling back on normal fetchgit. Original failure:\n\n"
761− repo = N $ dropGitExtension $ T.pack repo'
756+ errorToWarning e
762− commit = N commit'
757+ let s' = forgetGithub True s
763− branch = N <$> branch'
758+ (,) (ThunkSource_Git s') <$> gitThunkRev s' commit
764− mauth <- liftIO $ getHubAuth "github.com"
759+ ThunkSource_Git s -> (,) (ThunkSource_Git s) <$> gitThunkRev s commit
765− repoResult <- liftIO $ executeRequestMaybe mauth $ repositoryR owner repo
760+ pure $ ThunkPtr
766− repoIsPrivate <- either (liftIO . throwIO) (return . repoPrivate) repoResult
761+ { _thunkPtr_rev = rev
767− archiveResult <- liftIO $ executeRequestMaybe mauth $
762+ , _thunkPtr_source = src
768− archiveForR owner repo ArchiveFormatTarball (Just (untagName commit))
763+ }
769− archiveUri <- either (liftIO . throwIO) return archiveResult
764+
770− hash <- getNixSha256ForUriUnpacked archiveUri
765+-- | N.B. Cannot infer all fields.
771− return $ ThunkPtr
766+--
772− { _thunkPtr_rev = ThunkRev
767+-- If the thunk is a GitHub thunk and fails, we do *not* fall back like with
773− { _thunkRev_commit = commitNameToRef commit
768+-- `uriThunkPtr`. Unlike a plain URL, a thunk src explicitly states which method
774− , _thunkRev_nixSha256 = hash
769+-- should be employed, and so we respect that.
775− }
770+uriToThunkSource :: URI -> Maybe Text -> ThunkSource
776− , _thunkPtr_source = ThunkSource_GitHub $ GitHubSource
771+uriToThunkSource u
777− { _gitHubSource_owner = owner
772+ | Right uriAuth <- uriAuthority u
778− , _gitHubSource_repo = repo
773+ , Just scheme <- unRText <$> uriScheme u
779− , _gitHubSource_branch = branch
774+ , case scheme of
780− , _gitHubSource_private = repoIsPrivate
775+ "ssh" -> uriAuth == Authority
781− }
776+ { authUserInfo = Just $ UserInfo (fromRight' $ mkUsername "git") Nothing
782− }
777+ , authHost = fromRight' $ mkHost "github.com"
783− gitThunkPtr u commit branch' = do
778+ , authPort = Nothing
784− let protocols = ["https:", "ssh:", "git:"]
785− unless (T.toLower (T.pack $ uriScheme u) `elem` protocols) $
786− failWith $ "thunk pack: obelisk currently only supports "
787− <> T.intercalate ", " protocols <> " protocols for non-GitHub remotes"
788− hash <- nixPrefetchGit u commit False
789− branch <- case branch' of
790− Nothing -> failWith "thunk pack: git thunks must track a branch"
791− Just b -> pure $ N b
792− pure $ ThunkPtr
793− { _thunkPtr_rev = ThunkRev
794− { _thunkRev_commit = commitNameToRef (N commit)
795− , _thunkRev_nixSha256 = hash
796− }
797− , _thunkPtr_source = ThunkSource_Git $ GitSource
798− { _gitSource_url = u
799− , _gitSource_branch = branch
800− , _gitSource_fetchSubmodules = False -- TODO: How do we determine if this should be true?
801 }
779 }
780+ s -> s `L.elem` [ "git", "https", "http" ] -- "http:" just redirects to "https:"
781+ && unRText (authHost uriAuth) == "github.com"
782+ , Just (_, owner :| [repoish]) <- uriPath u
783+ = \mbranch -> ThunkSource_GitHub $ GitHubSource
784+ { _gitHubSource_owner = N $ unRText owner
785+ , _gitHubSource_repo = N $ let
786+ repoish' = unRText repoish
787+ in case T.stripSuffix ".git" repoish' of
788+ Just repo -> repo
789+ Nothing -> repoish'
790+ , _gitHubSource_branch = N <$> mbranch
802− }
791+ }
792+
793+ | otherwise = \mbranch -> ThunkSource_Git $ GitSource
794+ { _gitSource_url = u
795+ , _gitSource_branch = N <$> mbranch
796+ , _gitSource_fetchSubmodules = False -- TODO: How do we determine if this should be true?
797+ }
798+
799+-- Funny signature indicates no effects depend on the optional branch name.
800+githubThunkRev
801+ :: forall m
802+ . MonadObelisk m
803+ => GitHubSource
804+ -> Text
805+ -> m ThunkRev
806+githubThunkRev s commit = do
807+ owner <- forcePP $ _gitHubSource_owner s
808+ repo <- forcePP $ _gitHubSource_repo s
809+ revTarball <- mkPathPiece $ commit <> ".tar.gz"
810+ let archiveUri = URI
811+ { uriScheme = Just $ fromRight' $ mkScheme "https"
812+ , uriAuthority = Right $ Authority
813+ { authUserInfo = Nothing
814+ , authHost = fromRight' $ mkHost "github.com"
815+ , authPort = Nothing
816+ }
817+ , uriPath = Just ( False
818+ , owner :| [ repo, fromRight' $ mkPathPiece "archive", revTarball ]
819+ )
820+ , uriQuery = []
821+ , uriFragment = Nothing
822+ }
823+ hash <- getNixSha256ForUriUnpacked archiveUri
824+ putLog Debug $ "Nix sha256 is " <> hash
825+ return $ ThunkRev
826+ { _thunkRev_commit = commitNameToRef $ N commit
827+ , _thunkRev_nixSha256 = hash
828+ }
829+ where
830+ forcePP :: Name entity -> m (RText 'PathPiece)
831+ forcePP = mkPathPiece . untagName
832+
833+gitThunkRev
834+ :: MonadObelisk m
835+ => GitSource
836+ -> Text
837+ -> m ThunkRev
838+gitThunkRev s commit = do
839+ let u = _gitSource_url s
840+ protocols = ["https", "ssh", "git"]
841+ Just scheme <- pure $ unRText <$> uriScheme u
842+ unless (T.toLower scheme `elem` protocols) $
843+ failWith $ "obelisk currently only supports "
844+ <> T.intercalate ", " protocols <> " protocols for non-GitHub remotes"
845+ hash <- nixPrefetchGit u commit $ _gitSource_fetchSubmodules s
846+ putLog Informational $ "Nix sha256 is " <> hash
847+ pure $ ThunkRev
848+ { _thunkRev_commit = commitNameToRef (N commit)
849+ , _thunkRev_nixSha256 = hash
850+ }
851+
852+-- | Given the URI to a git remote, and an optional branch name, return the name
853+-- of the branch along with the hash of the commit at tip of that branch.
854+--
855+-- If the branch name is passed in, it is returned exactly as-is. If it is not
856+-- passed it, the default branch of the repo is used instead.
857+gitGetCommitBranch
858+ :: MonadObelisk m => URI -> Maybe Text -> m (Text, CommitId)
859+gitGetCommitBranch uri mbranch = withExitFailMessage ("Failure for git remote " <> uriMsg) $ do
860+ bothMaps <- gitLsRemote
861+ (T.unpack $ render uri)
862+ (GitRef_Branch <$> mbranch)
863+ branch <- case mbranch of
864+ Nothing -> withExitFailMessage "Failed to find default branch" $ do
865+ b <- rethrowE $ gitLookupDefaultBranch bothMaps
866+ putLog Debug $ "Default branch for remote repo " <> uriMsg <> " is " <> b
867+ pure b
868+ Just b -> pure b
869+ commit <- rethrowE $ gitLookupCommitForRef bothMaps (GitRef_Branch branch)
870+ putLog Informational $ "Latest commit in branch " <> branch
871+ <> " from remote repo " <> uriMsg
872+ <> " is " <> commit
873+ pure (branch, commit)
874+ where
875+ rethrowE = either failWith pure
876+ uriMsg = render uri
803
877
804parseGitUri :: Text -> Maybe URI
878parseGitUri :: Text -> Maybe URI
805−parseGitUri x = parseURIReference (T.unpack x) <|> parseSshShorthand x
879+parseGitUri x = parseAbsoluteURI x <|> parseSshShorthand x
880+
881+parseAbsoluteURI :: Text -> Maybe URI
882+parseAbsoluteURI uri = do
883+ parsedUri <- mkURI uri
884+ guard $ isPathAbsolute parsedUri
885+ pure $ parsedUri
806
886
807parseSshShorthand :: Text -> Maybe URI
887parseSshShorthand :: Text -> Maybe URI
808parseSshShorthand uri = do
888parseSshShorthand uri = do
@@ -817,4 +897,4 @@ parseSshShorthand uri = do
817 -- This check is used to disambiguate a filepath containing a colon from shorthand
897 -- This check is used to disambiguate a filepath containing a colon from shorthand
818 guard $ isNothing (T.findIndex (=='/') authAndHostname)
898 guard $ isNothing (T.findIndex (=='/') authAndHostname)
819 && not (T.null colonAndPath)
899 && not (T.null colonAndPath)
820− parseURI $ T.unpack properUri
900+ mkURI properUri
lib/command/src/Obelisk/Command/Upgrade.hsdeleted−293
@@ -1,293 +0,0 @@
1−{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3−{-# LANGUAGE LambdaCase #-}
4−{-# LANGUAGE MultiParamTypeClasses #-}
5−{-# LANGUAGE OverloadedStrings #-}
6−{-# LANGUAGE ScopedTypeVariables #-}
7−{-# LANGUAGE TypeApplications #-}
8−module Obelisk.Command.Upgrade where
9−
10−import Control.Monad (forM, unless, void)
11−import Control.Monad.Catch (onException)
12−import Control.Monad.Except
13−import Control.Monad.IO.Class (liftIO)
14−import Data.Maybe (catMaybes)
15−import Data.Monoid (Any (..), getAny)
16−import Data.Semigroup ((<>))
17−import qualified Data.Set as S
18−import Data.Text (Text)
19−import qualified Data.Text as T
20−import System.Directory
21−import System.FilePath
22−import System.Posix.Process (executeFile)
23−
24−import Obelisk.App (MonadObelisk)
25−import Obelisk.CliApp
26−import Obelisk.Command.Utils
27−
28−import Obelisk.Command.Project (toImplDir)
29−import Obelisk.Command.Project (findProjectObeliskCommand)
30−import Obelisk.Command.Thunk (ThunkData (..), getThunkGitBranch, readThunk, updateThunk)
31−
32−import Obelisk.Command.Upgrade.Hash
33−import Obelisk.Migration
34−
35−newtype HandOffAction = HandoffAction Any
36− deriving (Monoid, Semigroup, Ord, Eq, Show)
37−
38−instance Action HandOffAction where
39− parseEdgeMeta = HandoffAction . Any . (== "True")
40−
41−data MigrationGraph
42− = MigrationGraph_ObeliskUpgrade
43− | MigrationGraph_ObeliskHandoff
44− deriving (Eq, Show, Bounded, Enum)
45−
46−graphName :: MigrationGraph -> Text
47−graphName = \case
48− MigrationGraph_ObeliskHandoff -> "obelisk-handoff"
49− MigrationGraph_ObeliskUpgrade -> "obelisk-upgrade"
50−
51−fromGraphName :: Text -> MigrationGraph
52−fromGraphName = \case
53− "obelisk-handoff" -> MigrationGraph_ObeliskHandoff
54− "obelisk-upgrade" -> MigrationGraph_ObeliskUpgrade
55− _ -> error "Invalid graph name specified"
56−
57−graphNames :: [Text]
58−graphNames = fmap graphName $ [minBound .. maxBound]
59−
60−ensureCleanProject :: MonadObelisk m => FilePath -> m ()
61−ensureCleanProject project =
62− ensureCleanGitRepo project False "Cannot upgrade with uncommited changes"
63−
64−-- | Decide whether we (ambient ob) should handoff to project obelisk before performing upgrade
65−decideHandOffToProjectOb :: MonadObelisk m => FilePath -> m Bool
66−decideHandOffToProjectOb project = do
67− ensureCleanProject project
68− updateThunk (toImplDir project) $ \projectOb -> do
69− (ambientGraph, ambientHash) <- getAmbientObInfo
70− projectHash <- computeVertexHash projectOb
71− case hasVertex projectHash ambientGraph of
72− False -> do
73− putLog Warning "Project ob not found in ambient ob's migration graph; handing off anyway"
74− return True
75− True -> handleMigrationError (findPathAction ambientGraph projectHash ambientHash) >>= \case
76− Nothing -> do
77− putLog Warning "No migration path between project and ambient ob; handing off anyway"
78− return True
79− Just (HandoffAction dontHandoff) -> do
80− return $ not $ getAny dontHandoff
81− where
82− getAmbientObInfo = do
83− ambientOb <- getAmbientOb
84− getMigrationGraph' ambientOb MigrationGraph_ObeliskHandoff >>= \case
85− Nothing -> do
86− failWith "Ambient ob has no migration (this can't be possible)"
87− Just m -> do
88− -- We don't have ambient's ob source code, so locate its hash from the
89− -- graph. The last vertex should be it.
90− ambientHash <- handleMigrationError $ getLast (_migration_graph m)
91− return (m, ambientHash)
92−
93−-- | Return the path to the current ('ambient') obelisk process Nix directory
94−getAmbientOb :: MonadObelisk m => m FilePath
95−getAmbientOb = takeDirectory . takeDirectory <$> liftIO getObeliskExe
96−
97−upgradeObelisk :: MonadObelisk m => FilePath -> Maybe Text -> m ()
98−upgradeObelisk project gitBranchM = do
99− ensureCleanProject project
100− gitBranch <- maybe (getObeliskBranch project) pure gitBranchM
101− updateObelisk project gitBranch >>= handOffToNewOb project
102−
103−getObeliskBranch :: MonadObelisk m => FilePath -> m Text
104−getObeliskBranch project = readThunk (toImplDir project) >>= \case
105− Left e -> failWith $ T.pack $ show e
106− Right (ThunkData_Checkout _) -> failWith "obelisk thunk must be packed"
107− Right (ThunkData_Packed tptr) -> case getThunkGitBranch tptr of
108− Just v -> pure v
109− Nothing ->
110− failWith "You must run `ob upgrade` with the Git branch specified explicitly, as the project's obelisk thunk does not specify any."
111−
112−updateObelisk :: MonadObelisk m => FilePath -> Text -> m Hash
113−updateObelisk project gitBranch =
114− withSpinner ("Fetching new obelisk [" <> gitBranch <> "]") $
115− updateThunk (toImplDir project) $ \obImpl -> do
116− fromHash <- computeVertexHash obImpl
117− callProcessAndLogOutput (Debug, Debug) $
118− gitProc obImpl ["checkout", T.unpack gitBranch]
119− callProcessAndLogOutput (Debug, Debug) $
120− gitProc obImpl ["pull"]
121− return fromHash
122−
123−handOffToNewOb :: MonadObelisk m => FilePath -> Hash -> m ()
124−handOffToNewOb project fromHash = do
125− impl <- withSpinner' "Preparing for handoff" (Just $ ("Handed off to new obelisk " <>) . T.pack) $
126− findProjectObeliskCommand project >>= \case
127− Nothing -> failWith "Not an obelisk project"
128− Just impl -> pure impl
129− let opts = ["internal", "migrate", T.unpack fromHash]
130− liftIO $ executeFile impl False ("--no-handoff" : opts) Nothing
131−
132−migrateObelisk :: MonadObelisk m => FilePath -> Hash -> m ()
133−migrateObelisk project fromHash = void $ withSpinner' "Migrating to new obelisk" (Just id) $ do
134− updateThunk (toImplDir project) $ \obImpl -> revertObImplOnFail obImpl $ do
135− toHash <- computeVertexHash obImpl
136− g <- getMigrationGraph' obImpl MigrationGraph_ObeliskUpgrade >>= \case
137− Nothing -> failWith "New obelisk has no migration metadata"
138− Just m -> pure m
139−
140− unless (hasVertex fromHash g) $ do
141− failWith $ "Current obelisk hash " <> fromHash <> " missing in migration graph of new obelisk"
142− unless (hasVertex toHash g) $ do
143− -- This usually means that the target obelisk branch does not have
144− -- migration vertex for its latest commit; typically due to developer
145− -- negligence.
146− failWith $ "New obelisk hash " <> toHash <> " missing in its migration graph"
147−
148− if fromHash == toHash
149− then do
150− pure $ "No upgrade available (new Obelisk is the same)"
151− else do
152− putLog Debug $ "Migrating from " <> fromHash <> " to " <> toHash
153− handleMigrationError (findPathAction g fromHash toHash) >>= \case
154− Nothing -> do
155− failWith "Unable to find migration path"
156− Just action -> do
157− unless (action == mempty) $ do
158− putLog Notice "To upgrade your project to the new version of obelisk, please follow these instructions:\n"
159− putLog Notice action
160− pure $ "Migrated from " <> fromHash <> " to " <> toHash
161− where
162− revertObImplOnFail impl f = f `onException` do
163− putLog Notice $ T.pack $ "Reverting changes to " <> impl
164− callProcessAndLogOutput (Notice, Notice) $ gitProc project ["checkout", impl]
165−
166−-- | Get the migration graph for project
167−getMigrationGraph
168− :: (Action action, MonadObelisk m)
169− => FilePath -> MigrationGraph -> m (Migration action)
170−getMigrationGraph obDir graph = getMigrationGraph' obDir graph >>= \case
171− Nothing -> failWith "Migration graph missing"
172− Just m -> pure m
173−
174−getMigrationGraph'
175− :: (Action action, MonadObelisk m)
176− => FilePath -> MigrationGraph -> m (Maybe (Migration action))
177−getMigrationGraph' obDir graph = do
178− let name = graphName graph
179− dir = migrationDir obDir
180− putLog Debug $ "Reading migration graph " <> name <> " from " <> T.pack dir
181− liftIO $ readGraph dir name
182−
183−computeVertexHash :: MonadObelisk m => FilePath -> m Hash
184−computeVertexHash = getDirectoryHash [migrationDirName]
185−
186−-- | Verify the integrity of the migration graph in relation to the Git repo.
187−verifyMigration :: MonadObelisk m => FilePath -> m ()
188−verifyMigration obDir = do
189− upgradeGraph <- withSpinner "Reading migration graph" $
190− getMigrationGraph @Text obDir MigrationGraph_ObeliskUpgrade
191− withSpinner "Checking graph integrity" $
192− handleMigrationError $ ensureGraphIntegrity upgradeGraph
193− headVertex <- liftIO (doesDirectoryExist $ obDir </> ".git") >>= \case
194− True ->
195− withSpinner "Checking existence of HEAD vertex" $
196− getHeadVertex obDir
197− False -> do
198− hash <- computeVertexHash obDir
199− unless (hasVertex hash upgradeGraph) $
200− failWith $ "No vertex found for obelisk hash " <> hash
201− pure hash
202− withSpinner "Checking graph linearity" $
203− -- NOTE: We don't support merge commits (i.e., diamonds in a graph) yet.
204− -- Until we do, ensure that the graph is a fully connected linear list.
205− runExceptT (ensureGraphLinearity upgradeGraph) >>= \case
206− Left (NonLinearGraph_MultipleAdjacents vertex) ->
207− failWith $ "Graph is not linear; branches at vertex: " <> tshow vertex
208− Left (NonLinearGraph_NotConnected isolatedVertices) ->
209− failWith $ "Graph is partly linear, with isolated vertices: " <> tshow isolatedVertices
210− Left e ->
211− failWith $ "Error in migration graph: " <> tshow e
212− Right lastVertex -> unless (lastVertex == headVertex) $
213− failWith $ "Graph is linear, but its last vertex (" <> lastVertex <> ") is different from head vertex (" <> headVertex <> ")"
214−
215−-- | Create an edge from HEAD vertex to the hash corresponding to the Git
216−-- working copy. The HEAD vertex must already exist.
217−createMigrationEdgeFromHEAD :: MonadObelisk m => FilePath -> m ()
218−createMigrationEdgeFromHEAD obDir = do
219− headHash <- getHeadVertex obDir
220− wcHash <- getDirectoryHash migrationIgnore obDir
221− if (headHash == wcHash)
222− then
223− putLog Warning "No migration necessary (working copy has the same hash as that of HEAD)"
224− else do
225− writeEdge (migrationDir obDir) graphNames headHash wcHash >>= \case
226− Nothing ->
227− putLog Warning "No migration was created"
228− Just edgeDir ->
229− putLog Notice $ "Created edge: " <> T.pack edgeDir
230−
231−-- | Return the hash corresponding to HEAD
232−--
233−-- Fail if the hash does not exist in project's migration graph.
234−getHeadVertex :: MonadObelisk m => FilePath -> m Hash
235−getHeadVertex obDir = do
236− projectGraph <- getMigrationGraph @Text obDir MigrationGraph_ObeliskUpgrade
237− [headHash] <- getHashAtGitRevision ["HEAD"] migrationIgnore obDir
238− unless (hasVertex headHash projectGraph) $ do
239− -- This means that the HEAD commit has no vertex in the graph,
240− -- possibly due to developer negligence when commiting it.
241− -- Perhaps we should use a post-commit hook or some such thing
242− -- to reject such commits in the first place? For now, just
243− -- error out.
244− failWith $ "No vertex found for HEAD (" <> headHash <> ")"
245− return headHash
246−
247−-- | Create, or update, the migration graph with new edges and new vertices
248−-- corresponding to every commit in the Git history from HEAD.
249−--
250−-- NOTE: This creates a linear graph, and doesn't follow the Git graph
251−-- structure.
252−backfillGraph :: MonadObelisk m => Maybe Int -> FilePath -> m ()
253−backfillGraph lastN obDir = do
254− revs <- fmap (takeM lastN . fmap (fst . T.breakOn " ") . T.lines) $
255− readProc $ gitProc obDir ["log", "--pretty=oneline"]
256− -- Note: we need to take unique hashes only; this is fine for backfilling.
257− -- But future migrations should ensure that there are no duplicate hashes
258− -- (which would cause cycles) such as those introduced by revert commits.
259− vertices :: [Hash] <- withSpinnerNoTrail "Computing hash for git history" $
260− fmap (unique . reverse) $ getHashAtGitRevision revs [migrationDirName] obDir
261− void $ withSpinner'
262− ("Backfilling with " <> tshow (length vertices) <> " vertices")
263− (Just $ \n -> "Backfilled " <> tshow n <> " edges.") $ do
264− let vertexPairs = zip vertices $ drop 1 vertices
265− let edgesDir = migrationDir obDir
266− liftIO $ createDirectoryIfMissing False edgesDir
267− fmap (length . catMaybes) $ forM vertexPairs $ \(v1, v2) -> do
268− writeEdge edgesDir graphNames v1 v2
269− where
270− -- Return unique items in the list /while/ preserving order
271− unique = loop mempty
272− where
273− loop _ [] = []
274− loop s (x : xs)
275− | S.member x s = loop s xs
276− | otherwise = x : loop (S.insert x s) xs
277− takeM n' xs = case n' of
278− Just n -> take n xs
279− Nothing -> xs
280−
281−handleMigrationError :: MonadObelisk m => Except MigrationError a -> m a
282−handleMigrationError m = case runExcept m of
283− Left e -> failWith $ "Error in migration graph: " <> tshow e
284− Right a -> return a
285−
286−migrationDir :: FilePath -> FilePath
287−migrationDir project = project </> migrationDirName
288−
289−migrationIgnore :: [FilePath]
290−migrationIgnore = [migrationDirName]
291−
292−migrationDirName :: FilePath
293−migrationDirName = "migration"
lib/command/src/Obelisk/Command/Upgrade/Hash.hs+4−5
@@ -102,15 +102,14 @@ tidyUpGitWorkingCopy :: MonadObelisk m => FilePath -> m ()
102tidyUpGitWorkingCopy dir = withSpinnerNoTrail "Tidying up git working copy" $ do
102tidyUpGitWorkingCopy dir = withSpinnerNoTrail "Tidying up git working copy" $ do
103 ignored <- gitLsFiles dir ["--ignored", "--exclude-standard", "--others"]
103 ignored <- gitLsFiles dir ["--ignored", "--exclude-standard", "--others"]
104 untracked <- gitLsFiles dir ["--exclude-standard", "--others"]
104 untracked <- gitLsFiles dir ["--exclude-standard", "--others"]
105− putLog Debug $ T.pack $ "Ignored: " <> show (length ignored) <> " files."
105+ putLog Debug $ "Ignored: " <> T.pack (show $ length ignored) <> " files."
106− putLog Debug $ T.pack $ "Untracked files:\n" <> unlines untracked
106+ putLog Debug $ "Untracked files:\n" <> T.unlines untracked
107 withSpinnerNoTrail "Removing untracked and ignored files" $ do
107 withSpinnerNoTrail "Removing untracked and ignored files" $ do
108− forM_ (fmap (dir </>) $ ignored <> untracked) $
108+ forM_ (fmap ((dir </>) . T.unpack) $ ignored <> untracked) $
109 liftIO . removePathForcibly
109 liftIO . removePathForcibly
110 -- Empty directories won't be included in these lists. Git doesn't track them
110 -- Empty directories won't be included in these lists. Git doesn't track them
111 -- So we must delete these separately.
111 -- So we must delete these separately.
112 runProc $ proc "find" [dir, "-depth", "-empty", "-type", "d", "-delete"]
112 runProc $ proc "find" [dir, "-depth", "-empty", "-type", "d", "-delete"]
113 where
113 where
114− gitLsFiles pwd opts = fmap lines $ readProcessAndLogStderr Error $
114+ gitLsFiles pwd opts = fmap T.lines $ readProc $
115 (proc "git" $ ["ls-files", "."] <> opts) { cwd = Just pwd }
115 (proc "git" $ ["ls-files", "."] <> opts) { cwd = Just pwd }
116−
lib/command/src/Obelisk/Command/Utils.hs+104−6
@@ -1,16 +1,30 @@
1{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE MultiWayIf #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PatternGuards #-}
4module Obelisk.Command.Utils where
7module Obelisk.Command.Utils where
5
8
6−import Control.Applicative (liftA2)
9+import Control.Applicative hiding (many)
10+import Control.Monad.Except
7import Data.Bool (bool)
11import Data.Bool (bool)
12+import qualified Text.Megaparsec.Char.Lexer as ML
13+import Data.Bifunctor
14+import Data.Char
15+import Data.Either
8import Data.Semigroup ((<>))
16import Data.Semigroup ((<>))
17+import Data.Map (Map)
18+import qualified Data.Map as M
19+import Data.Maybe (maybeToList)
9import Data.Text (Text)
20import Data.Text (Text)
10import qualified Data.Text as T
21import qualified Data.Text as T
22+import Data.Void (Void)
11import System.Directory (canonicalizePath)
23import System.Directory (canonicalizePath)
12import System.Environment (getExecutablePath)
24import System.Environment (getExecutablePath)
13import qualified System.Process as P
25import qualified System.Process as P
26+import Text.Megaparsec as MP
27+import Text.Megaparsec.Char as MP
14
28
15import Obelisk.App (MonadObelisk)
29import Obelisk.App (MonadObelisk)
16import Obelisk.CliApp
30import Obelisk.CliApp
@@ -25,7 +39,7 @@ checkGitCleanStatus repo withIgnored = do
25 runGit = readProcessAndLogStderr Debug . gitProc repo
39 runGit = readProcessAndLogStderr Debug . gitProc repo
26 gitStatus = runGit $ ["status", "--porcelain"] <> bool [] ["--ignored"] withIgnored
40 gitStatus = runGit $ ["status", "--porcelain"] <> bool [] ["--ignored"] withIgnored
27 gitDiff = runGit ["diff"]
41 gitDiff = runGit ["diff"]
28− null <$> liftA2 (<>) gitStatus gitDiff
42+ T.null <$> liftA2 (<>) gitStatus gitDiff
29
43
30-- | Ensure that git repo is clean
44-- | Ensure that git repo is clean
31ensureCleanGitRepo :: MonadObelisk m => FilePath -> Bool -> Text -> m ()
45ensureCleanGitRepo :: MonadObelisk m => FilePath -> Bool -> Text -> m ()
@@ -46,9 +60,11 @@ initGit repo = do
46 git ["add", "."]
60 git ["add", "."]
47 git ["commit", "-m", "Initial commit."]
61 git ["commit", "-m", "Initial commit."]
48
62
63+gitProcNoRepo :: [String] -> P.CreateProcess
64+gitProcNoRepo = P.proc "git"
65+
49gitProc :: FilePath -> [String] -> P.CreateProcess
66gitProc :: FilePath -> [String] -> P.CreateProcess
50−gitProc repo argsRaw =
67+gitProc repo = gitProcNoRepo . runGitInDir
51− P.proc "git" $ runGitInDir argsRaw
52 where
68 where
53 runGitInDir args' = case filter (not . null) args' of
69 runGitInDir args' = case filter (not . null) args' of
54 args@("clone":_) -> args <> [repo]
70 args@("clone":_) -> args <> [repo]
@@ -60,7 +76,7 @@ copyDir src dest =
60 (P.proc "cp" ["-a", ".", dest]) { P.cwd = Just src }
76 (P.proc "cp" ["-a", ".", dest]) { P.cwd = Just src }
61
77
62readGitProcess :: MonadObelisk m => FilePath -> [String] -> m Text
78readGitProcess :: MonadObelisk m => FilePath -> [String] -> m Text
63−readGitProcess repo = fmap T.pack . readProcessAndLogStderr Notice . gitProc repo
79+readGitProcess repo = readProcessAndLogOutput (Debug, Notice) . gitProc repo
64
80
65processToShellString :: FilePath -> [String] -> String
81processToShellString :: FilePath -> [String] -> String
66processToShellString cmd args = unwords $ map quoteAndEscape (cmd : args)
82processToShellString cmd args = unwords $ map quoteAndEscape (cmd : args)
@@ -76,7 +92,89 @@ runProcSilently = callProcessAndLogOutput (Debug, Debug)
76
92
77-- | A simpler wrapper for CliApp's readProcessAndLogStderr with sensible defaults.
93-- | A simpler wrapper for CliApp's readProcessAndLogStderr with sensible defaults.
78readProc :: MonadObelisk m => P.CreateProcess -> m Text
94readProc :: MonadObelisk m => P.CreateProcess -> m Text
79−readProc = fmap T.pack . readProcessAndLogStderr Error
95+readProc = readProcessAndLogOutput (Debug, Error)
80
96
81tshow :: Show a => a -> Text
97tshow :: Show a => a -> Text
82tshow = T.pack . show
98tshow = T.pack . show
99+
100+gitLookupDefaultBranch :: GitLsRemoteMaps -> Either Text Text
101+gitLookupDefaultBranch (refs, _) = do
102+ ref <- case M.lookup GitRef_Head refs of
103+ Just ref -> pure ref
104+ Nothing -> throwError
105+ "No symref entry for HEAD. \
106+ \ Is your git version at least 1.8.5? \
107+ \ Otherwise `git ls-remote --symref` will not work."
108+ case ref of
109+ GitRef_Branch b -> pure b
110+ _ -> throwError $
111+ "Default ref " <> showGitRef ref <> " is not a branch!"
112+
113+gitLookupCommitForRef :: GitLsRemoteMaps -> GitRef -> Either Text CommitId
114+gitLookupCommitForRef (_, commits) ref = case M.lookup ref commits of
115+ Just a -> pure a
116+ Nothing -> throwError $ "Did not find commit for " <> showGitRef ref
117+
118+gitLsRemote
119+ :: MonadObelisk m
120+ => String
121+ -> Maybe GitRef
122+ -> m GitLsRemoteMaps
123+gitLsRemote repository mRef = do
124+ t <- readProc $ gitProcNoRepo $
125+ ["ls-remote", "--symref", repository]
126+ ++ (maybeToList $ T.unpack . showGitRef <$> mRef)
127+ maps <- case MP.runParser parseLsRemote "" t of
128+ Left err -> failWith $ T.pack $ MP.parseErrorPretty' t err
129+ Right table -> pure $ bimap M.fromList M.fromList $ partitionEithers $ table
130+ putLog Debug $ "git ls-remote maps: " <> T.pack (show maps)
131+ pure maps
132+
133+lexeme :: Parsec Void Text a -> Parsec Void Text a
134+lexeme = ML.lexeme $ void $ MP.takeWhileP (Just "within-line white space") $
135+ flip elem [' ', '\t']
136+
137+-- $ git ls-remote --symref git@github.com:obsidiansystems/obelisk.git HEAD
138+-- ref: refs/heads/master HEAD
139+-- d0a8d25dc93f0acd096bc4ff2f550da9e2d0c8f5 refs/heads/master
140+parseLsRemote :: Parsec Void Text [Either (GitRef, GitRef) (GitRef, CommitId)]
141+parseLsRemote =
142+ many ((fmap Left (try parseRef) <|> fmap Right parseCommit) <* try MP.eol) <* MP.eof
143+ where
144+ parseRef :: Parsec Void Text (GitRef, GitRef)
145+ parseRef = MP.label "ref and symbolic ref" $ do
146+ _ <- lexeme "ref:"
147+ ref <- lexeme $ MP.takeWhileP (Just "ref") $ not . isSpace
148+ symbolicRef <- lexeme $ MP.takeWhileP (Just "symbolic ref") $ not . isSpace
149+ return (toGitRef symbolicRef, toGitRef ref)
150+ parseCommit :: Parsec Void Text (GitRef, CommitId)
151+ parseCommit = MP.label "commit and ref" $ do
152+ commitId <- lexeme $ MP.takeWhileP (Just "commit id") $ not . isSpace
153+ ref <- lexeme $ MP.takeWhileP (Just "ref") $ not . isSpace
154+ return (toGitRef ref, commitId)
155+
156+data GitRef
157+ = GitRef_Head
158+ | GitRef_Branch Text
159+ | GitRef_Tag Text
160+ | GitRef_Other Text
161+ deriving (Show, Eq, Ord)
162+
163+showGitRef :: GitRef -> Text
164+showGitRef = \case
165+ GitRef_Head -> "HEAD"
166+ GitRef_Branch x -> "refs/heads/" <> x
167+ GitRef_Tag x -> "refs/tags/" <> x
168+ GitRef_Other x -> x
169+
170+toGitRef :: Text -> GitRef
171+toGitRef = \case
172+ "HEAD" -> GitRef_Head
173+ r -> if
174+ | Just s <- "refs/heads/" `T.stripPrefix` r -> GitRef_Branch s
175+ | Just s <- "refs/tags/" `T.stripPrefix` r -> GitRef_Tag s
176+ | otherwise -> GitRef_Other r
177+
178+type CommitId = Text
179+
180+type GitLsRemoteMaps = (Map GitRef GitRef, Map GitRef CommitId)
lib/command/src/Obelisk/Command/VmBuilder.hs+59−30
@@ -1,13 +1,13 @@
1{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE QuasiQuotes #-}
3{-# LANGUAGE QuasiQuotes #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
4module Obelisk.Command.VmBuilder where
5module Obelisk.Command.VmBuilder where
5
6
6import Control.Applicative (liftA2)
7import Control.Applicative (liftA2)
7−import Control.Monad (unless)
8+import Control.Monad (when, unless)
8−import Control.Monad.Catch (try)
9+import Control.Monad.Catch (handle)
9import Control.Monad.IO.Class (liftIO)
10import Control.Monad.IO.Class (liftIO)
10−import Data.Either (isRight)
11import Data.Monoid ((<>))
11import Data.Monoid ((<>))
12import Data.String (IsString)
12import Data.String (IsString)
13import Data.String.Here.Uninterpolated (hereLit)
13import Data.String.Here.Uninterpolated (hereLit)
@@ -15,7 +15,7 @@ import Data.Text (Text)
15import qualified Data.Text as T
15import qualified Data.Text as T
16import qualified Data.Text.IO as T
16import qualified Data.Text.IO as T
17import System.Directory (createDirectoryIfMissing)
17import System.Directory (createDirectoryIfMissing)
18−import System.Exit (ExitCode)
18+import System.Exit (ExitCode(..))
19import System.FilePath ((<.>), (</>))
19import System.FilePath ((<.>), (</>))
20import qualified System.Info
20import qualified System.Info
21import System.Process (proc)
21import System.Process (proc)
@@ -28,10 +28,33 @@ import Obelisk.Command.Nix (withNixRemoteCheck)
28getNixBuildersArg :: MonadObelisk m => m String
28getNixBuildersArg :: MonadObelisk m => m String
29getNixBuildersArg = do
29getNixBuildersArg = do
30 stateDir <- liftIO getDockerBuilderStateDir
30 stateDir <- liftIO getDockerBuilderStateDir
31+ let sshIdFile = stateDir </> sshKeyFileName
32+ checkForNixDarwin sshIdFile
31− exists <- containerExists
33+ exists <- containerExists stateDir
32 if exists then startContainer else setupNixDocker stateDir
34 if exists then startContainer else setupNixDocker stateDir
33 pure $ nixBuildersArgString stateDir
35 pure $ nixBuildersArgString stateDir
34
36
37+checkForNixDarwin :: MonadObelisk m => String -> m ()
38+checkForNixDarwin sshIdFile = do
39+ (exitCode, _, _) <- readCreateProcessWithExitCode $ proc "which" ["darwin-rebuild"]
40+ unless (exitCode == ExitSuccess) $ failWith $ T.intercalate "\n"
41+ [ "Deployments from macOS require nix-darwin to be installed."
42+ , "Follow the installation instructions here: https://github.com/LnL7/nix-darwin"
43+ , "You'll also need to add the following to your 'configuration.nix' and run 'darwin-rebuild switch':"
44+ , ""
45+ , T.intercalate "\n" $ map (" " <>)
46+ [ "nix.distributedBuilds = true;"
47+ , "nix.buildMachines = [{"
48+ , " hostName = " <> T.pack (show (containerName :: String)) <> ";"
49+ , " sshUser = \"root\";"
50+ , " sshKey = " <> T.pack (show sshIdFile) <> ";"
51+ , " sshPort = " <> T.pack (show containerSshPort) <> ";"
52+ , " systems = [ \"x86_64-linux\" ];"
53+ , " maxJobs = 2;"
54+ , "}];"
55+ ]
56+ ]
57+
35-- | String to pass to nix's `--builders` arguments to enable the VM builder.
58-- | String to pass to nix's `--builders` arguments to enable the VM builder.
36nixBuildersArgString :: FilePath -> String
59nixBuildersArgString :: FilePath -> String
37nixBuildersArgString stateDir = unwords [containerName, "x86_64-linux", stateDir </> sshKeyFileName, "1", "1", "kvm"]
60nixBuildersArgString stateDir = unwords [containerName, "x86_64-linux", stateDir </> sshKeyFileName, "1", "1", "kvm"]
@@ -41,14 +64,19 @@ containerName :: IsString str => str
41containerName = "obelisk-docker-nix-builder"
64containerName = "obelisk-docker-nix-builder"
42
65
43-- | Check to see if the Docker container exists. This will exit with a helpful message if Docker is not installed.
66-- | Check to see if the Docker container exists. This will exit with a helpful message if Docker is not installed.
44−containerExists :: MonadObelisk m => m Bool
67+containerExists :: MonadObelisk m => FilePath -> m Bool
45−containerExists = withExitFailMessage needDockerMsg $ do
68+containerExists stateDir = handle (\(_ :: IOError) -> failWith needDockerMsg) $ do
46− containerNames <- fmap (map T.strip . T.lines . T.pack) $
69+ containerNames <- fmap (map T.strip . T.lines) $
47 readProcessAndLogStderr Error $
70 readProcessAndLogStderr Error $
48 proc "docker" ["container", "list", "--all", "--format", "{{.Names}}"]
71 proc "docker" ["container", "list", "--all", "--format", "{{.Names}}"]
49− pure $ containerName `elem` containerNames
72+ let exists = containerName `elem` containerNames
73+ when exists $ testLinuxBuild stateDir
74+ pure exists
50 where
75 where
76+ needDockerMsg = T.intercalate "\n"
51− needDockerMsg = "This feature requires that you have Docker installed and the `docker` command available on your PATH. Please go https://docs.docker.com/ to install Docker and try this command again."
77+ [ "This feature requires that you have Docker installed and the `docker` command available on your PATH."
78+ , "Please go https://docs.docker.com/ to install Docker and try this command again."
79+ ]
52
80
53-- | SSH port on localhost that connects to the container.
81-- | SSH port on localhost that connects to the container.
54containerSshPort :: Int
82containerSshPort :: Int
@@ -74,25 +102,20 @@ setupNixDocker stateDir = withSpinner ("Creating Docker container named " <> con
74 proc "ssh-keygen" ["-t", "ed25519", "-f", stateDir </> sshKeyFileName, "-P", ""]
102 proc "ssh-keygen" ["-t", "ed25519", "-f", stateDir </> sshKeyFileName, "-P", ""]
75
103
76 -- Build the docker container (which uses the SSH keys in the 'ssh' folder)
104 -- Build the docker container (which uses the SSH keys in the 'ssh' folder)
77− containerId <- readProcessAndLogStderr Error $
105+ containerId <- fmap T.strip $ readProcessAndLogStderr Error $
78 proc "docker" ["build", stateDir, "--quiet"]
106 proc "docker" ["build", stateDir, "--quiet"]
79 callProcessAndLogOutput (Debug, Error) $ proc "docker"
107 callProcessAndLogOutput (Debug, Error) $ proc "docker"
80 [ "run"
108 [ "run"
81 , "--restart", "always"
109 , "--restart", "always"
82 , "--detach"
110 , "--detach"
83 , "--publish", show containerSshPort <> ":22"
111 , "--publish", show containerSshPort <> ":22"
84− , "--name", containerName, containerId
112+ , "--name", T.unpack containerName, T.unpack containerId
85 ]
113 ]
86− exists <- containerExists
114+ exists <- containerExists stateDir
87 unless exists $
115 unless exists $
88 failWith $ "Expected to see docker container named " <> containerName <> " but it does not exist."
116 failWith $ "Expected to see docker container named " <> containerName <> " but it does not exist."
89
117
90− linuxBuildWorked <- if System.Info.os == "linux"
91− then pure False -- The Linux test is useless on Linux so just assume the setup is incomplete.
92− else testLinuxBuild stateDir
118+ testLinuxBuild stateDir
93− unless linuxBuildWorked $ do
94− let sshIdFile = stateDir </> sshKeyFileName
95− failWith $ setupInstructions sshIdFile
96
119
97-- | The instructions for setting up SSH access to the container for the Nix daemon.
120-- | The instructions for setting up SSH access to the container for the Nix daemon.
98setupInstructions :: FilePath -> Text
121setupInstructions :: FilePath -> Text
@@ -129,19 +152,24 @@ getDockerBuilderStateDir :: IO FilePath
129getDockerBuilderStateDir = liftA2 (</>) getObeliskUserStateDir (pure "nix-docker-builder")
152getDockerBuilderStateDir = liftA2 (</>) getObeliskUserStateDir (pure "nix-docker-builder")
130
153
131-- | Run a test build to see if a Linux build can finish successfully.
154-- | Run a test build to see if a Linux build can finish successfully.
132−testLinuxBuild :: MonadObelisk m => FilePath -> m Bool
155+testLinuxBuild :: MonadObelisk m => FilePath -> m ()
133−testLinuxBuild stateDir =
156+testLinuxBuild stateDir
157+ | System.Info.os == "linux" = failWith "Using the docker builder is not necessary on linux."
134− fmap (isRight :: Either ExitCode () -> Bool) $ try $ withNixRemoteCheck $
158+ | otherwise = withNixRemoteCheck $ do
135− callProcessAndLogOutput (Debug, Debug) $ proc "nix-build"
159+ (exitCode, _stdout, stderr) <- readCreateProcessWithExitCode $ proc "nix-build"
136− [ "-E", "(import <nixpkgs> { system = \"x86_64-linux\"; }).writeText \"test\" builtins.currentTime"
160+ [ "-E", "(import <nixpkgs> { system = \"x86_64-linux\"; }).writeText \"test\" builtins.currentTime"
137− , "--builders", nixBuildersArgString stateDir
161+ , "--builders", nixBuildersArgString stateDir
138− ]
162+ ]
139−
163+ unless (exitCode == ExitSuccess) $ do
164+ let sshIdFile = stateDir </> sshKeyFileName
165+ putLog Error $ T.pack stderr
166+ failWith $ setupInstructions sshIdFile
167+
140−-- Copied from https://raw.githubusercontent.com/LnL7/nix-docker/bf28b99aebd8e403d2fc2171f4fa8878f857171c/ssh/Dockerfile
168+-- Copied from https://github.com/LnL7/nix-docker/blob/8dcfb3aff1f87cdafeecb0d27964b27c3fb8b1d2/ssh/Dockerfile
141-- Renamed "insecure_rsa" to 'sshKeyFileName'
169-- Renamed "insecure_rsa" to 'sshKeyFileName'
142dockerfile :: Text
170dockerfile :: Text
143dockerfile = [hereLit|
171dockerfile = [hereLit|
144−FROM lnl7/nix:2018-04-17
172+FROM lnl7/nix:2018-09-21
145
173
146RUN nix-env -f '<nixpkgs>' -iA \
174RUN nix-env -f '<nixpkgs>' -iA \
147 gnused \
175 gnused \
@@ -166,7 +194,8 @@ RUN mkdir -p /etc/ssh \
166 ] <> [hereLit|
194 ] <> [hereLit|
167
195
168EXPOSE 22
196EXPOSE 22
169−CMD ["/nix/store/hpnx760s247labqc3nbn2kripk73p0ca-openssh-7.6p1/bin/sshd", "-D", "-e"]
197+CMD ["/nix/store/mydwfxzk8bka4iwjml033dir6gkmqwic-openssh-7.7p1/bin/sshd", "-D", "-e"]
198+
170|]
199|]
171
200
172sshKeyFileName :: FilePath
201sshKeyFileName :: FilePath
lib/executable-config/default.nix+2−1
@@ -7,7 +7,8 @@
7}:
7}:
8let injectConfig = config: assets: nixpkgs.runCommand "inject-config" {} ''
8let injectConfig = config: assets: nixpkgs.runCommand "inject-config" {} ''
9 set -x
9 set -x
10+ mkdir -p $out
10− cp -a "${assets}" $out
11+ cp --no-preserve=mode -Lr "${assets}" $out/static
11 chmod +w "$out"
12 chmod +w "$out"
12 if ! mkdir $out/config; then
13 if ! mkdir $out/config; then
13 2>&1 echo config directory already exists or could not be created
14 2>&1 echo config directory already exists or could not be created
lib/frontend/src/Obelisk/Frontend.hs+14−16
@@ -31,8 +31,6 @@ import Data.ByteString (ByteString)
31import Data.Dependent.Sum (DSum (..))
31import Data.Dependent.Sum (DSum (..))
32import Data.Functor.Sum
32import Data.Functor.Sum
33import Data.IORef
33import Data.IORef
34−import Data.Monoid hiding (Sum)
35−import Data.Text (Text)
36import GHCJS.DOM hiding (bracket, catch)
34import GHCJS.DOM hiding (bracket, catch)
37import GHCJS.DOM.Document
35import GHCJS.DOM.Document
38import GHCJS.DOM.Node
36import GHCJS.DOM.Node
@@ -64,13 +62,12 @@ type ObeliskWidget t x route m =
64 , MonadFix (Performable m)
62 , MonadFix (Performable m)
65 , PrimMonad m
63 , PrimMonad m
66 , Prerender x m
64 , Prerender x m
67− , EventWriter t (Endo route) m
65+ , SetRoute t route m
68 )
66 )
69
67
70data Frontend route = Frontend
68data Frontend route = Frontend
71 { _frontend_head :: !(forall t m x. ObeliskWidget t x route m => RoutedT t route m ())
69 { _frontend_head :: !(forall t m x. ObeliskWidget t x route m => RoutedT t route m ())
72 , _frontend_body :: !(forall t m x. ObeliskWidget t x route m => RoutedT t route m ())
70 , _frontend_body :: !(forall t m x. ObeliskWidget t x route m => RoutedT t route m ())
73− , _frontend_notFoundRoute :: !(Text -> route) --TODO: Instead, maybe we should just require that the `parse` Monad for routeEncoder be `Identity`
74 }
71 }
75
72
76type Widget' x = ImmediateDomBuilderT DomTimeline (DomCoreWidget x)
73type Widget' x = ImmediateDomBuilderT DomTimeline (DomCoreWidget x)
@@ -130,9 +127,12 @@ runWithHeadAndBody app = withJSContextSingletonMono $ \jsSing -> do
130 _ -> return ()
127 _ -> return ()
131 return postBuildTriggerRef
128 return postBuildTriggerRef
132
129
133−runFrontend :: forall backendRoute route. ValidEncoder (Either Text) (R (Sum backendRoute (ObeliskRoute route))) PageName -> Frontend (R route) -> JSM ()
130+runFrontend :: forall backendRoute route. Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName -> Frontend (R route) -> JSM ()
134runFrontend validFullEncoder frontend = do
131runFrontend validFullEncoder frontend = do
135− let ve = validFullEncoder . prismValidEncoder (rPrism $ _InR . _ObeliskRoute_App)
132+ let ve = validFullEncoder . hoistParse errorLeft (prismEncoder (rPrism $ _InR . _ObeliskRoute_App))
133+ errorLeft = \case
134+ Left _ -> error "runFrontend: Unexpected non-app ObeliskRoute reached the frontend. This shouldn't happen."
135+ Right x -> Identity x
136 runMyRouteViewT
136 runMyRouteViewT
137 :: ( TriggerEvent t m
137 :: ( TriggerEvent t m
138 , PerformEvent t m
138 , PerformEvent t m
@@ -142,24 +142,22 @@ runFrontend validFullEncoder frontend = do
142 , MonadFix m
142 , MonadFix m
143 , MonadFix (Performable m)
143 , MonadFix (Performable m)
144 )
144 )
145− => RoutedT t (R route) (EventWriterT t (Endo (R route)) m) a
145+ => RoutedT t (R route) (SetRouteT t (R route) m) a
146 -> m a
146 -> m a
147− runMyRouteViewT = runRouteViewT
147+ runMyRouteViewT = runRouteViewT ve
148− ve
149− (_frontend_notFoundRoute frontend)
150 runWithHeadAndBody $ \appendHead appendBody -> runMyRouteViewT $ do
148 runWithHeadAndBody $ \appendHead appendBody -> runMyRouteViewT $ do
151− mapRoutedT (mapEventWriterT appendHead) $ _frontend_head frontend
149+ mapRoutedT (mapSetRouteT appendHead) $ _frontend_head frontend
152− mapRoutedT (mapEventWriterT appendBody) $ _frontend_body frontend
150+ mapRoutedT (mapSetRouteT appendBody) $ _frontend_body frontend
153
151
154renderFrontendHtml
152renderFrontendHtml
155− :: (Semigroup w, t ~ SpiderTimeline Global)
153+ :: (t ~ SpiderTimeline Global)
156 => r
154 => r
157− -> RoutedT t r (EventWriterT t w (PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global))))) ()
155+ -> RoutedT t r (SetRouteT t r' (PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global))))) ()
158− -> RoutedT t r (EventWriterT t w (PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global))))) ()
156+ -> RoutedT t r (SetRouteT t r' (PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global))))) ()
159 -> IO ByteString
157 -> IO ByteString
160renderFrontendHtml route headWidget bodyWidget = do
158renderFrontendHtml route headWidget bodyWidget = do
161 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
159 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
162− html <- fmap snd $ renderStatic $ fmap fst $ runEventWriterT $ flip runRoutedT (pure route) $
160+ html <- fmap snd $ renderStatic $ fmap fst $ runSetRouteT $ flip runRoutedT (pure route) $
163 el "html" $ do
161 el "html" $ do
164 el "head" headWidget
162 el "head" headWidget
165 el "body" bodyWidget
163 el "body" bodyWidget
lib/migration/obelisk-migration.cabaldeleted−24
@@ -1,24 +0,0 @@
1−name: obelisk-migration
2−version: 0.1
3−license: BSD3
4−build-type: Simple
5−cabal-version: >=1.2
6−
7−library
8− hs-source-dirs: src
9− build-depends:
10− algebraic-graphs >= 0.2
11− , base
12− , containers
13− , directory
14− , exceptions
15− , filepath
16− , mtl
17− , obelisk-cliapp
18− , process
19− , text
20− , transformers
21− exposed-modules:
22− Obelisk.Migration
23− other-modules:
24− ghc-options: -Wall -fobject-code
lib/migration/src/Obelisk/Migration.hsdeleted−223
@@ -1,223 +0,0 @@
1−{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE LambdaCase #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE TupleSections #-}
6−module Obelisk.Migration where
7−
8−import Control.Monad (forM, forM_, unless, void)
9−import Control.Monad.Except
10−import Control.Monad.IO.Class (MonadIO, liftIO)
11−import Control.Monad.State.Strict (evalStateT, get, put)
12−import Data.Map (Map)
13−import qualified Data.Map as Map
14−import Data.Maybe (catMaybes)
15−import Data.Semigroup ((<>))
16−import Data.Set (Set)
17−import qualified Data.Set as Set
18−import Data.Text (Text)
19−import qualified Data.Text as T
20−import qualified Data.Text.IO as T
21−import System.Directory
22−import System.FilePath
23−
24−import Algebra.Graph.AdjacencyMap
25−
26−type Hash = Text
27−
28−-- Migration graph with edge metadata
29−data Migration action = Migration
30− { _migration_graph :: AdjacencyMap Hash
31− , _migration_actions :: Map (Hash, Hash) action -- Edge metadata
32− }
33− deriving (Eq, Show)
34−
35−-- A path is a list of edges
36−type Path = [(Hash, Hash)]
37−
38−data MigrationError
39− = GraphInternalError Text
40− | CyclicGraphError
41− | MultipleTerminalVertices
42− | NoTerminalVertex
43− | NonEquivalentPaths Hash Hash
44− | MissingEdgeInActionMap (Hash, Hash)
45− | NonLinearGraph_MultipleAdjacents Hash
46− | NonLinearGraph_NotConnected (Set Hash)
47− deriving (Eq, Ord, Show)
48−
49−class Action action where
50− parseEdgeMeta :: Text -> action
51−
52−instance Action Text where
53− parseEdgeMeta = id
54−
55−-- | Get the directed-acylic graph of migration
56−-- TODO: Consider calling getDag only when reading the graph for efficiency.
57−getDag :: (MonadError MigrationError m, Ord a) => AdjacencyMap a -> m (AdjacencyMap a)
58−getDag g = case topSort g of
59− Just _ -> pure g
60− Nothing -> throwError CyclicGraphError
61−
62−-- | Read the graph from a directory of files.
63−readGraph :: Action action => FilePath -> Text -> IO (Maybe (Migration action))
64−readGraph root name = doesDirectoryExist root >>= \case
65− False -> return Nothing
66− True -> do
67− allVertexPairs <- listDirectory root >>= return . catMaybes . fmap getEdgeVertices
68− edgesInfo <- fmap catMaybes $ forM allVertexPairs $ \vs -> do
69− getEdgeFor name vs >>= return . fmap (vs, )
70− return $ pure $ Migration (edges $ fmap fst edgesInfo) (Map.fromList edgesInfo)
71− where
72− getEdgeVertices p = case T.splitOn "-" $ T.pack p of
73− [v1, v2] -> Just (v1, v2)
74− _ -> Nothing
75− getEdgeFor graph (v1, v2) = do
76− let f = root </> (T.unpack $ v1 <> "-" <> v2) </> T.unpack graph
77− doesFileExist f >>= \case
78− True -> do
79− c <- parseEdgeMeta <$> T.readFile f
80− return $ Just c
81− False -> do
82− return Nothing
83−
84−-- | Ensure that the migration graph is correct
85−--
86−-- In a correct migration graph: for all pairs of nodes, all paths between
87−-- those nodes have the same migration value
88−--
89−-- Throw the following if integrity check fails
90−-- * `CyclicGraphError`
91−-- * `NonEquivalentPaths`
92−--
93−-- Complexity: O(V+E)
94−ensureGraphIntegrity
95− :: (MonadError MigrationError m, Monoid action, Ord action)
96− => Migration action -> m ()
97−ensureGraphIntegrity m = do
98− graph <- fmap adjacencyMap $ getDag $ _migration_graph m
99− firstVertex <- getFirst $ _migration_graph m
100− let
101− visitFrom visited acc start = case Map.lookup start graph of
102− Nothing -> pure ()
103− Just adjs -> void $ flip traverse (Set.toList adjs) $ \adj -> do
104− acc' <- fmap (mappend acc) $ getAction m (start, adj)
105− visited' <- case Map.lookup adj visited of
106− Nothing -> pure $ Map.insert adj acc' visited
107− Just adjAcc -> if acc' == adjAcc
108− then pure visited
109− else throwError $ NonEquivalentPaths start adj
110− visitFrom visited' acc' adj
111− -- We only check all paths from the _first_ vertex; it is not necessary to
112− -- check all paths between _all_ pairs of vertices as that can be proved by
113− -- induction, as long as there is exactly one first vertex (which we check on
114− -- further above).
115− visitFrom mempty mempty firstVertex
116−
117−-- | Ensure that the graph is a fully connected linear list
118−--
119−-- Returns the last vertex in the linear list.
120−ensureGraphLinearity :: MonadError MigrationError m => Migration action -> m Hash
121−ensureGraphLinearity m = do
122− graph <- fmap adjacencyMap $ getDag $ _migration_graph m
123− firstVertex <- getFirst $ _migration_graph m
124− let
125− allVertices = vertexSet $ _migration_graph m
126− visitFrom start = case Map.lookup start graph of
127− Nothing -> throwError $ GraphInternalError $ "Vertex not found: " <> start
128− Just adjs -> case (Set.toList adjs) of
129− [] -> pure (start, [start])
130− [adj] -> fmap (start :) <$> visitFrom adj
131− _ -> throwError $ NonLinearGraph_MultipleAdjacents start
132− (lastVertex, visitedVertices) <- visitFrom firstVertex
133− let unvisitedVertices = Set.difference allVertices $ Set.fromList visitedVertices
134− if Set.null unvisitedVertices
135− then pure lastVertex
136− else throwError $ NonLinearGraph_NotConnected unvisitedVertices
137−
138−-- | Find the concatenated actions between two vertices
139−--
140−-- All paths between the vertices must have the same mappend'ed action;
141−-- otherwise this will throw.
142−--
143−-- Complexity: O(V+E)
144−findPathAction
145− :: (MonadError MigrationError m, Action action, Monoid action, Ord action)
146− => Migration action -> Hash -> Hash -> m (Maybe action)
147−findPathAction m start end = flip evalStateT Map.empty $ do
148− graph <- fmap adjacencyMap $ getDag $ _migration_graph m
149− let
150− findPathFromCached start' = do
151− -- TODO: refactor the caching part into a separate `memoize` function
152− cache <- get
153− case Map.lookup start' cache of
154− Just result ->
155− pure result
156− Nothing -> do
157− result <- findPathFrom start'
158− put $ Map.insert start' result cache
159− pure result
160− findPathFrom start' = if start' == end
161− then
162− pure $ Just mempty
163− else case Map.lookup start' graph of
164− Just adjs -> do
165− actions <- fmap catMaybes $ flip traverse (Set.toList adjs) $ \adj -> do
166− action0 <- getAction m (start', adj)
167− actionM <- findPathFromCached adj
168− pure $ fmap (mappend action0) $ actionM
169− case uniqs actions of
170− [] -> pure Nothing
171− [v] -> pure $ Just v -- Exactly one monoidal value; accept.
172− _ -> throwError $ NonEquivalentPaths start' end
173− Nothing ->
174− pure Nothing
175− uniqs = Set.toList . Set.fromList
176− findPathFromCached start
177−
178−hasVertex :: Hash -> Migration action -> Bool
179−hasVertex h (Migration g _) = Set.member h $ vertexSet g
180−
181−-- | Get the action for the given edge (which must exist)
182−getAction :: MonadError MigrationError m => Migration action -> (Hash, Hash) -> m action
183−getAction (Migration _ h) e = case Map.lookup e h of
184− Just v -> pure v
185− Nothing -> throwError $ MissingEdgeInActionMap e
186−
187−-- | Get the last vertex of the given DAG. Nothing if empty graph.
188−--
189−-- Assumes that the graph is fully connected.
190−getLast :: (MonadError MigrationError m, Ord a, Eq a) => AdjacencyMap a -> m a
191−getLast g = lastVertices >>= \case
192− [] -> throwError NoTerminalVertex
193− [x] -> pure x
194− _ -> throwError MultipleTerminalVertices
195− where
196− lastVertices
197− = pure
198− . fmap fst
199− . filter (\(_, nexts) -> nexts == [])
200− . adjacencyList
201− =<< getDag g
202−
203−-- | Get the first vertex of the given DAG. Nothing if empty graph.
204−getFirst :: (MonadError MigrationError m, Ord a, Eq a) => AdjacencyMap a -> m a
205−getFirst = getLast . transpose
206−
207−-- | Write an edge directly to the filesystem.
208−--
209−-- Actions are written with empty string.
210−--
211−-- Return the edge directory created.
212−writeEdge :: MonadIO m => FilePath -> [Text] -> Hash -> Hash -> m (Maybe FilePath)
213−writeEdge dir actionFiles v1 v2 = do
214− unless (v1 /= v2) $
215− fail $ "Cannot create self loop with: " <> T.unpack v1
216− let edgeDir = dir </> (T.unpack $ v1 <> "-" <> v2)
217− liftIO (doesDirectoryExist edgeDir) >>= \case
218− True -> pure Nothing
219− False -> do
220− liftIO $ createDirectory edgeDir
221− forM_ actionFiles $ \fp -> liftIO $
222− writeFile (edgeDir </> T.unpack fp) ""
223− pure $ Just edgeDir
lib/route/obelisk-route.cabal+2−1
@@ -1,5 +1,5 @@
1name: obelisk-route
1name: obelisk-route
2−version: 0.1
2+version: 0.2
3cabal-version: >= 1.2
3cabal-version: >= 1.2
4build-type: Simple
4build-type: Simple
5
5
@@ -11,6 +11,7 @@ library
11 containers,
11 containers,
12 dependent-map,
12 dependent-map,
13 dependent-sum,
13 dependent-sum,
14+ dependent-sum-universe-orphans,
14 dependent-sum-template,
15 dependent-sum-template,
15 either,
16 either,
16 ghcjs-dom,
17 ghcjs-dom,
lib/route/src/Obelisk/Route.hs+404−300
@@ -20,40 +20,50 @@ module Obelisk.Route
20 ( R
20 ( R
21 , pattern (:/)
21 , pattern (:/)
22 , PageName
22 , PageName
23+ , PathQuery
23− , Encoder (..) --TODO: unsafe
24+ , Encoder
25+ , unsafeEncoder
24 , checkEncoder
26 , checkEncoder
25− , ValidEncoder (..) --TODO: unsafe
27+ , EncoderImpl (..)
26− , ValidEncoderFunc (..)
28+ , EncoderFunc (..)
29+ , unsafeMkEncoder
30+ , encode
31+ , decode
32+ , tryDecode
33+ , hoistCheck
34+ , hoistParse
27 , mapSome
35 , mapSome
36+ , SegmentResult (..)
28 , pathComponentEncoder
37 , pathComponentEncoder
29 , enumEncoder
38 , enumEncoder
30 , enum1Encoder
39 , enum1Encoder
31 , checkEnum1EncoderFunc
40 , checkEnum1EncoderFunc
32− , endValidEncoder
41+ , unitEncoder
33− , pathOnlyValidEncoder
42+ , pathOnlyEncoder
34− , singletonListValidEncoder
43+ , singletonListEncoder
35 , unpackTextEncoder
44 , unpackTextEncoder
36 , prefixTextEncoder
45 , prefixTextEncoder
37− , unsafeTshowValidEncoder
46+ , unsafeTshowEncoder
38− , someConstValidEncoder
47+ , someConstEncoder
39− , singlePathSegmentValidEncoder
48+ , singlePathSegmentEncoder
40− , justValidEncoder
49+ , maybeEncoder
41− , nothingValidEncoder
50+ , maybeToEitherEncoder
42− , isoValidEncoder
51+ , justEncoder
43− , wrappedValidEncoder
52+ , nothingEncoder
44− , unwrappedValidEncoder
53+ , isoEncoder
54+ , wrappedEncoder
55+ , unwrappedEncoder
45 , listToNonEmptyEncoder
56 , listToNonEmptyEncoder
46 , prefixNonemptyTextEncoder
57 , prefixNonemptyTextEncoder
47 , joinPairTextEncoder
58 , joinPairTextEncoder
48 , toListMapEncoder
59 , toListMapEncoder
49 , shadowEncoder
60 , shadowEncoder
50− , prismValidEncoder
61+ , prismEncoder
51 , rPrism
62 , rPrism
52 , obeliskRouteEncoder
63 , obeliskRouteEncoder
53− , obeliskRouteComponentEncoder
64+ , obeliskRouteSegment
54− , checkObeliskRouteRestEncoder
65+ , pageNameEncoder
55− , pageNameValidEncoder
66+ , handleEncoder
56− , catchValidEncoder
57 , ObeliskRoute (..)
67 , ObeliskRoute (..)
58 , _ObeliskRoute_App
68 , _ObeliskRoute_App
59 , _ObeliskRoute_Resource
69 , _ObeliskRoute_Resource
@@ -61,11 +71,13 @@ module Obelisk.Route
61 , JSaddleWarpRoute (..)
71 , JSaddleWarpRoute (..)
62 , jsaddleWarpRouteEncoder
72 , jsaddleWarpRouteEncoder
63 , IndexOnlyRoute (..)
73 , IndexOnlyRoute (..)
64− , indexOnlyRouteComponentEncoder
74+ , indexOnlyRouteSegment
65− , indexOnlyRouteRestEncoder
75+ , indexOnlyRouteEncoder
66 , someSumEncoder
76 , someSumEncoder
67 , Void1
77 , Void1
68 , void1Encoder
78 , void1Encoder
79+ , pathSegmentsTextEncoder
80+ , queryParametersTextEncoder
69 ) where
81 ) where
70
82
71import Prelude hiding ((.), id)
83import Prelude hiding ((.), id)
@@ -95,6 +107,7 @@ import Data.Set (Set)
95import qualified Data.Set as Set
107import qualified Data.Set as Set
96import Data.Some (Some)
108import Data.Some (Some)
97import qualified Data.Some as Some
109import qualified Data.Some as Some
110+import Data.Some.Universe.Orphans ()
98import Data.Text (Text)
111import Data.Text (Text)
99import qualified Data.Text as T
112import qualified Data.Text as T
100import Data.Text.Encoding
113import Data.Text.Encoding
@@ -139,41 +152,83 @@ mapSome f (Some.This a) = Some.This $ f a
139-- Encoder fundamentals
152-- Encoder fundamentals
140--------------------------------------------------------------------------------
153--------------------------------------------------------------------------------
141
154
155+-- | This is the type of route encoder/decoders. It is parameterised over two monads: Firstly, the monad
156+-- used to check the validity of the encoder (i.e. that it is total), secondly the monad used for parsing
157+-- during the decode phase. The following two parameters are respectively the type of decoded data, and the
158+-- encoded type.
142−newtype Encoder check parse decoded encoded = Encoder { unEncoder :: check (ValidEncoder parse decoded encoded) }
159+newtype Encoder check parse decoded encoded =
143−
160+ Encoder { unEncoder :: check (EncoderImpl parse decoded encoded) }
161+
162+unsafeEncoder :: check (EncoderImpl parse decoded encoded) -> Encoder check parse decoded encoded
163+unsafeEncoder = Encoder
164+
165+-- | The internal type used to construct primitive 'Encoder' values.
144−-- | Law:
166+-- Law:
145−-- forall p. _validEncoder_decode ve . _validEncoder_encode ve p == pure
167+-- forall p. _encoderImpl_decode ve . _encoderImpl_encode ve p == pure
146−-- Note that the reverse may not be the case: when parsing, a route may be canonicalized, and erroneous routes may be collapsed to a single 404 route. However, as a consequence of the law, encode . decode must be idemponent.
168+-- Note that the reverse may not be the case: when parsing, a route may be canonicalized, and erroneous routes may be collapsed to a single 404 route. However, as a consequence of the law, encode . decode must be idempotent.
147−data ValidEncoder parse decoded encoded = ValidEncoder
169+data EncoderImpl parse decoded encoded = EncoderImpl
148− { _validEncoder_decode :: !(encoded -> parse decoded) -- Can fail; can lose information; must always succeed on outputs of `_validEncoder_encode` and result in the original value
170+ { _encoderImpl_decode :: !(encoded -> parse decoded) -- Can fail; can lose information; must always succeed on outputs of `_encoderImpl_encode` and result in the original value
149− , _validEncoder_encode :: !(decoded -> encoded) -- Must be injective
171+ , _encoderImpl_encode :: !(decoded -> encoded) -- Must be injective
150 }
172 }
151
173
152−checkEncoder :: Encoder check parse decoded encoded -> check (ValidEncoder parse decoded encoded)
174+-- | Once an 'Encoder' has been checked, so that its check monad has become 'Identity', and its parser is total
175+-- so that its parse monad is also 'Identity', it may be used to actually decode by applying this function.
176+decode :: Encoder Identity Identity decoded encoded -> encoded -> decoded
177+decode e x = runIdentity (tryDecode e x)
178+
179+-- | Once an 'Encoder' has been checked, so that its check monad has become 'Identity', even if the same is not true of the
180+-- parse monad, we may still attempt to decode with it in its parse monad.
181+tryDecode :: Encoder Identity parse decoded encoded -> encoded -> parse decoded
182+tryDecode (Encoder (Identity impl)) x = _encoderImpl_decode impl x
183+
184+-- | Similar to 'decode' above, once an encoder has been checked so that its check monad is Identity, it
185+-- can be used to actually encode by using this. Note that while there's no constraint on the parse monad here,
186+-- one should usually be applying decode and encode to the same 'Encoder'
187+encode :: Encoder Identity parse decoded encoded -> decoded -> encoded
188+encode (Encoder (Identity impl)) x = _encoderImpl_encode impl x
189+
190+-- | This is a primitive used to build encoders which can't fail to check. It should not be used unless one is
191+-- reasonably certain that the law given for 'EncoderImpl' above holds.
192+unsafeMkEncoder :: (Applicative check) => EncoderImpl parse decoded encoded -> Encoder check parse decoded encoded
193+unsafeMkEncoder impl = Encoder (pure impl)
194+
195+-- | Transform the check monad of an 'Encoder' by applying a natural transformation.
196+hoistCheck :: (forall t. check t -> check' t) -> Encoder check parse a b -> Encoder check' parse a b
197+hoistCheck f (Encoder x) = Encoder (f x)
198+
199+-- | Transform the parse monad of an 'Encoder' by applying a natural transformation.
200+hoistParse :: (Functor check)
201+ => (forall t. parse t -> parse' t) -> Encoder check parse a b -> Encoder check parse' a b
202+hoistParse f (Encoder x) = Encoder (fmap (\(EncoderImpl dec enc) -> EncoderImpl (f . dec) enc) x)
203+
204+-- | Check an 'Encoder', transforming it into one whose check monad is anything we want (usually Identity).
205+checkEncoder :: (Applicative check', Functor check)
206+ => Encoder check parse decoded encoded
207+ -> check (Encoder check' parse decoded encoded)
153−checkEncoder = unEncoder
208+checkEncoder = fmap unsafeMkEncoder . unEncoder
154
209
155instance (Applicative check, Monad parse) => Category (Encoder check parse) where
210instance (Applicative check, Monad parse) => Category (Encoder check parse) where
156 id = Encoder $ pure id
211 id = Encoder $ pure id
157 Encoder f . Encoder g = Encoder $ liftA2 (.) f g
212 Encoder f . Encoder g = Encoder $ liftA2 (.) f g
158
213
159−instance Monad parse => Category (ValidEncoder parse) where
214+instance Monad parse => Category (EncoderImpl parse) where
160− id = ValidEncoder
215+ id = EncoderImpl
161− { _validEncoder_decode = pure
216+ { _encoderImpl_decode = pure
162− , _validEncoder_encode = id
217+ , _encoderImpl_encode = id
163 }
218 }
164− f . g = ValidEncoder
219+ f . g = EncoderImpl
165− { _validEncoder_decode = _validEncoder_decode g <=< _validEncoder_decode f
220+ { _encoderImpl_decode = _encoderImpl_decode g <=< _encoderImpl_decode f
166− , _validEncoder_encode = _validEncoder_encode f . _validEncoder_encode g
221+ , _encoderImpl_encode = _encoderImpl_encode f . _encoderImpl_encode g
167 }
222 }
168
223
169−instance Monad parse => PFunctor (,) (ValidEncoder parse) (ValidEncoder parse) where
224+instance Monad parse => PFunctor (,) (EncoderImpl parse) (EncoderImpl parse) where
170 first f = bimap f id
225 first f = bimap f id
171−instance Monad parse => QFunctor (,) (ValidEncoder parse) (ValidEncoder parse) where
226+instance Monad parse => QFunctor (,) (EncoderImpl parse) (EncoderImpl parse) where
172 second g = bimap id g
227 second g = bimap id g
173−instance Monad parse => Bifunctor (,) (ValidEncoder parse) (ValidEncoder parse) (ValidEncoder parse) where
228+instance Monad parse => Bifunctor (,) (EncoderImpl parse) (EncoderImpl parse) (EncoderImpl parse) where
174− bimap f g = ValidEncoder
229+ bimap f g = EncoderImpl
175− { _validEncoder_encode = bimap (_validEncoder_encode f) (_validEncoder_encode g)
230+ { _encoderImpl_encode = bimap (_encoderImpl_encode f) (_encoderImpl_encode g)
176− , _validEncoder_decode = \(a, b) -> liftA2 (,) (_validEncoder_decode f a) (_validEncoder_decode g b)
231+ , _encoderImpl_decode = \(a, b) -> liftA2 (,) (_encoderImpl_decode f a) (_encoderImpl_decode g b)
177 }
232 }
178
233
179instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse) (Encoder check parse) where
234instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse) (Encoder check parse) where
@@ -181,46 +236,46 @@ instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse)
181instance (Applicative check, Monad parse) => QFunctor (,) (Encoder check parse) (Encoder check parse) where
236instance (Applicative check, Monad parse) => QFunctor (,) (Encoder check parse) (Encoder check parse) where
182 second g = bimap id g
237 second g = bimap id g
183instance (Applicative check, Monad parse) => Bifunctor (,) (Encoder check parse) (Encoder check parse) (Encoder check parse) where
238instance (Applicative check, Monad parse) => Bifunctor (,) (Encoder check parse) (Encoder check parse) (Encoder check parse) where
184− bimap f g = Encoder $ liftA2 bimap (checkEncoder f) (checkEncoder g)
239+ bimap f g = Encoder $ liftA2 bimap (unEncoder f) (unEncoder g)
185
240
186−instance (Traversable f, Monad parse) => Cat.Functor f (ValidEncoder parse) (ValidEncoder parse) where
241+instance (Traversable f, Monad parse) => Cat.Functor f (EncoderImpl parse) (EncoderImpl parse) where
187− fmap ve = ValidEncoder
242+ fmap ve = EncoderImpl
188− { _validEncoder_encode = fmap $ _validEncoder_encode ve
243+ { _encoderImpl_encode = fmap $ _encoderImpl_encode ve
189− , _validEncoder_decode = traverse $ _validEncoder_decode ve
244+ , _encoderImpl_decode = traverse $ _encoderImpl_decode ve
190 }
245 }
191
246
192instance (Traversable f, Monad check, Monad parse) => Cat.Functor f (Encoder check parse) (Encoder check parse) where
247instance (Traversable f, Monad check, Monad parse) => Cat.Functor f (Encoder check parse) (Encoder check parse) where
193 fmap e = Encoder $ do
248 fmap e = Encoder $ do
194− ve <- checkEncoder e
249+ ve <- unEncoder e
195 pure $ Cat.fmap ve
250 pure $ Cat.fmap ve
196
251
197−instance Monad parse => Associative (ValidEncoder parse) (,) where
252+instance Monad parse => Associative (EncoderImpl parse) (,) where
198− associate = ValidEncoder
253+ associate = EncoderImpl
199− { _validEncoder_encode = associate
254+ { _encoderImpl_encode = associate
200− , _validEncoder_decode = pure . disassociate
255+ , _encoderImpl_decode = pure . disassociate
201 }
256 }
202− disassociate = ValidEncoder
257+ disassociate = EncoderImpl
203− { _validEncoder_encode = disassociate
258+ { _encoderImpl_encode = disassociate
204− , _validEncoder_decode = pure . associate
259+ , _encoderImpl_decode = pure . associate
205 }
260 }
206
261
207−instance Monad parse => Monoidal (ValidEncoder parse) (,) where
262+instance Monad parse => Monoidal (EncoderImpl parse) (,) where
208− type Id (ValidEncoder parse) (,) = ()
263+ type Id (EncoderImpl parse) (,) = ()
209− idl = ValidEncoder
264+ idl = EncoderImpl
210− { _validEncoder_encode = idl
265+ { _encoderImpl_encode = idl
211− , _validEncoder_decode = pure . coidl
266+ , _encoderImpl_decode = pure . coidl
212 }
267 }
213− idr = ValidEncoder
268+ idr = EncoderImpl
214− { _validEncoder_encode = idr
269+ { _encoderImpl_encode = idr
215− , _validEncoder_decode = pure . coidr
270+ , _encoderImpl_decode = pure . coidr
216 }
271 }
217− coidl = ValidEncoder
272+ coidl = EncoderImpl
218− { _validEncoder_encode = coidl
273+ { _encoderImpl_encode = coidl
219− , _validEncoder_decode = pure . idl
274+ , _encoderImpl_decode = pure . idl
220 }
275 }
221− coidr = ValidEncoder
276+ coidr = EncoderImpl
222− { _validEncoder_encode = coidr
277+ { _encoderImpl_encode = coidr
223− , _validEncoder_decode = pure . idr
278+ , _encoderImpl_decode = pure . idr
224 }
279 }
225
280
226instance (Applicative check, Monad parse) => Associative (Encoder check parse) (,) where
281instance (Applicative check, Monad parse) => Associative (Encoder check parse) (,) where
@@ -234,53 +289,120 @@ instance (Applicative check, Monad parse) => Monoidal (Encoder check parse) (,)
234 coidl = Encoder $ pure coidl
289 coidl = Encoder $ pure coidl
235 coidr = Encoder $ pure coidr
290 coidr = Encoder $ pure coidr
236
291
237−
238--------------------------------------------------------------------------------
292--------------------------------------------------------------------------------
239-- Specific instances of encoders
293-- Specific instances of encoders
240--------------------------------------------------------------------------------
294--------------------------------------------------------------------------------
241
295
242−-- | A path and query string, in which trailing slashes don't matter in the path
296+-- | Given a valid 'Iso' from lens, construct an 'Encoder'
243−-- and duplicate query parameters are not allowed
297+isoEncoder :: (Applicative check, Applicative parse) => Iso' a b -> Encoder check parse a b
244−type PageName = ([Text], Map Text (Maybe Text))
298+isoEncoder f = unsafeMkEncoder $ EncoderImpl
299+ { _encoderImpl_encode = view f
300+ , _encoderImpl_decode = pure . view (from f)
301+ }
245
302
246−newtype Flip f a b = Flip { unFlip :: f b a }
303+wrappedEncoder :: (Wrapped a, Applicative check, Applicative parse) => Encoder check parse (Unwrapped a) a
304+wrappedEncoder = isoEncoder $ from _Wrapped'
247
305
248−someConstValidEncoder :: Applicative parse => ValidEncoder parse (Some (Const a)) a
306+unwrappedEncoder :: (Wrapped a, Applicative check, Applicative parse) => Encoder check parse a (Unwrapped a)
249−someConstValidEncoder = ValidEncoder
307+unwrappedEncoder = isoEncoder _Wrapped'
308+
309+maybeToEitherEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Maybe a) (Either () a)
310+maybeToEitherEncoder = unsafeMkEncoder $ EncoderImpl
311+ { _encoderImpl_encode = \case
312+ Nothing -> Left ()
313+ Just a -> Right a
314+ , _encoderImpl_decode = pure . \case
315+ Left _ -> Nothing
316+ Right a -> Just a
317+ }
318+
319+maybeEncoder
320+ :: ( MonadError Text check
321+ , Show a
322+ , Show b
323+ , check ~ parse
324+ )
325+ => Encoder check parse () b
326+ -> Encoder check parse a b
327+ -> Encoder check parse (Maybe a) b
328+maybeEncoder f g = shadowEncoder f g . maybeToEitherEncoder
329+
330+-- | Encode a value by simply applying 'Just'
331+justEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a (Maybe a)
332+justEncoder = prismEncoder _Just
333+
334+-- |
335+nothingEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse () (Maybe a)
336+nothingEncoder = prismEncoder _Nothing
337+
338+someConstEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (Const a)) a
339+someConstEncoder = unsafeMkEncoder $ EncoderImpl
250− { _validEncoder_encode = \(Some.This (Const a)) -> a
340+ { _encoderImpl_encode = \(Some.This (Const a)) -> a
251− , _validEncoder_decode = pure . Some.This . Const
341+ , _encoderImpl_decode = pure . Some.This . Const
252 }
342 }
253
343
254-- | WARNING: This is only safe if the Show and Read instances for 'a' are
344-- | WARNING: This is only safe if the Show and Read instances for 'a' are
255-- inverses of each other
345-- inverses of each other
256−unsafeTshowValidEncoder :: (Show a, Read a, MonadError Text parse) => ValidEncoder parse a Text
346+unsafeTshowEncoder :: (Show a, Read a, Applicative check, MonadError Text parse) => Encoder check parse a Text
257−unsafeTshowValidEncoder = ValidEncoder
347+unsafeTshowEncoder = unsafeMkEncoder $ EncoderImpl
258− { _validEncoder_encode = tshow
348+ { _encoderImpl_encode = tshow
259− , _validEncoder_decode = \raw -> case readMaybe $ T.unpack raw of
349+ , _encoderImpl_decode = \raw -> case readMaybe $ T.unpack raw of
260− Nothing -> throwError $ "unsafeTshowValidEncoder: couldn't decode " <> tshow raw
350+ Nothing -> throwError $ "unsafeTshowEncoderImpl: couldn't decode " <> tshow raw
261 Just parsed -> pure parsed
351 Just parsed -> pure parsed
262 }
352 }
263
353
264−justValidEncoder :: MonadError Text parse => ValidEncoder parse a (Maybe a)
354+newtype EncoderFunc check parse p r = EncoderFunc { runEncoderImplFunc :: forall a. p a -> Encoder check parse a r }
265−justValidEncoder = prismValidEncoder _Just
266−
267−nothingValidEncoder :: MonadError Text parse => ValidEncoder parse () (Maybe a)
268−nothingValidEncoder = prismValidEncoder _Nothing
269−
270−isoValidEncoder :: Applicative parse => Iso' a b -> ValidEncoder parse a b
271−isoValidEncoder f = ValidEncoder
272− { _validEncoder_encode = view f
273− , _validEncoder_decode = pure . view (from f)
274− }
275−
276−wrappedValidEncoder :: (Wrapped a, Applicative parse) => ValidEncoder parse (Unwrapped a) a
277−wrappedValidEncoder = isoValidEncoder $ from _Wrapped'
278
355
279−unwrappedValidEncoder :: (Wrapped a, Applicative parse) => ValidEncoder parse a (Unwrapped a)
356+newtype Flip f a b = Flip { unFlip :: f b a }
280−unwrappedValidEncoder = isoValidEncoder _Wrapped'
281
357
282−pathComponentEncoder :: forall check parse p. (Monad check, Monad parse) => (Encoder check parse (Some p) (Maybe Text)) -> (forall a. p a -> ValidEncoder parse a PageName) -> Encoder check parse (R p) PageName
358+checkEnum1EncoderFunc
359+ :: forall check check' parse p r.
360+ ( Universe (Some p)
361+ , GCompare p
362+ , Monad check
363+ , Applicative check'
364+ )
365+ => (forall a. p a -> Encoder check parse a r)
366+ -> check (EncoderFunc check' parse p r)
367+checkEnum1EncoderFunc f = do
368+ (encoderImpls :: DMap p (Flip (EncoderImpl parse) r)) <- DMap.fromList <$>
369+ traverse (\(Some.This p) -> (p :=>) . Flip <$> unEncoder (f p)) universe
370+ pure $ EncoderFunc $ \p -> unsafeMkEncoder . unFlip $
371+ DMap.findWithDefault (error "checkEnum1EncoderFunc: EncoderImpl not found (should be impossible)") p encoderImpls
372+
373+-- | This type is used by pathComponentEncoder to allow the user to indicate how to treat various cases when encoding a dependent sum of type `(R p)`.
374+data SegmentResult check parse a =
375+ PathEnd (Encoder check parse a (Map Text (Maybe Text))) -- ^ Indicate that the path is finished, with an Encoder that translates the corresponding value into query parameters
376+ | PathSegment Text (Encoder check parse a PageName) -- ^ Indicate that the key should be represented by an additional path segment with the given 'Text', and give an Encoder for translating the corresponding value into the remainder of the route.
377+
378+-- | Encode a dependent sum of type `(R p)` into a PageName (i.e. the path and query part of a URL) by using the
379+-- supplied function to decide how to encode the constructors of p using the SegmentResult type. It is important
380+-- that the number of values of type `(Some p)` be relatively small in order for checking to complete quickly.
381+pathComponentEncoder
382+ :: forall check parse p.
383+ ( Universe (Some p)
384+ , GShow p
385+ , GCompare p
386+ , MonadError Text check
387+ , MonadError Text parse )
388+ => (forall a. p a -> SegmentResult check parse a)
389+ -> Encoder check parse (R p) PageName
390+pathComponentEncoder f = Encoder $ do
391+ let extractEncoder = \case
392+ PathEnd e -> first (unitEncoder []) . coidl . e
393+ PathSegment _ e -> e
394+ extractPathSegment = \case
395+ PathEnd _ -> Nothing
396+ PathSegment t _ -> Just t
397+ EncoderFunc f' <- checkEnum1EncoderFunc (extractEncoder . f)
398+ unEncoder (pathComponentEncoderImpl (enum1Encoder (extractPathSegment . f)) f')
399+
400+pathComponentEncoderImpl :: forall check parse p. (Monad check, Monad parse)
401+ => (Encoder check parse (Some p) (Maybe Text))
402+ -> (forall a. p a -> Encoder Identity parse a PageName)
403+ -> Encoder check parse (R p) PageName
404+pathComponentEncoderImpl this rest =
283−pathComponentEncoder this rest = chainEncoder (lensEncoder (\(_, b) a -> (a, b)) Prelude.fst consValidEncoder) this rest
405+ chainEncoder (lensEncoder (\(_, b) a -> (a, b)) Prelude.fst consEncoder) this rest
284
406
285--NOTE: Naming convention in this module is to always talk about things in the *encoding* direction, never in the *decoding* direction
407--NOTE: Naming convention in this module is to always talk about things in the *encoding* direction, never in the *decoding* direction
286
408
@@ -289,34 +411,40 @@ chainEncoder
289 ( Monad check
411 ( Monad check
290 , Monad parse
412 , Monad parse
291 )
413 )
292− => ValidEncoder parse (b, r) r
414+ => Encoder check parse (b, r) r
293 -> Encoder check parse (Some p) b
415 -> Encoder check parse (Some p) b
294− -> (forall a. p a -> ValidEncoder parse a r)
416+ -> (forall a. p a -> Encoder Identity parse a r)
295 -> Encoder check parse (R p) r
417 -> Encoder check parse (R p) r
296chainEncoder cons this rest = Encoder $ do
418chainEncoder cons this rest = Encoder $ do
419+ consValid <- unEncoder cons
297− thisValid <- checkEncoder this
420+ thisValid <- unEncoder this
298− pure $ ValidEncoder
421+ pure $ EncoderImpl
299− { _validEncoder_decode = \v -> do
422+ { _encoderImpl_decode = \v -> do
300− (here, following) <- _validEncoder_decode cons v
423+ (here, following) <- _encoderImpl_decode consValid v
301− Some.This r <- _validEncoder_decode thisValid here
424+ Some.This r <- _encoderImpl_decode thisValid here
302− (r :/) <$> _validEncoder_decode (rest r) following
425+ (r :/) <$> _encoderImpl_decode (runIdentity . unEncoder $ rest r) following
303− , _validEncoder_encode = \(r :/ s) ->
426+ , _encoderImpl_encode = \(r :/ s) ->
304− _validEncoder_encode cons (_validEncoder_encode thisValid $ Some.This r, _validEncoder_encode (rest r) s)
427+ _encoderImpl_encode consValid
428+ ( _encoderImpl_encode thisValid $ Some.This r
429+ , _encoderImpl_encode (runIdentity . unEncoder $ rest r) s)
305 }
430 }
306
431
307--TODO: Do this in terms of a lens instead
432--TODO: Do this in terms of a lens instead
308−lensEncoder :: Monad parse => (b -> [a] -> b) -> (b -> [a]) -> ValidEncoder parse (c, [a]) [a] -> ValidEncoder parse (c, b) b
433+lensEncoder :: (Applicative check, Monad parse)
434+ => (b -> [a] -> b) -> (b -> [a]) -> Encoder check parse (c, [a]) [a] -> Encoder check parse (c, b) b
309−lensEncoder set get g = ValidEncoder
435+lensEncoder set get g = Encoder $ do
310− { _validEncoder_encode = \(ma, b) -> set b $ _validEncoder_encode g (ma, get b)
436+ gImpl <- unEncoder g
311− , _validEncoder_decode = \b -> do
437+ pure $ EncoderImpl
312− (ma, la) <- _validEncoder_decode g $ get b
438+ { _encoderImpl_encode = \(ma, b) -> set b $ _encoderImpl_encode gImpl (ma, get b)
439+ , _encoderImpl_decode = \b -> do
440+ (ma, la) <- _encoderImpl_decode gImpl $ get b
313− pure (ma, set b la)
441+ pure (ma, set b la)
314− }
442+ }
315
443
316−consValidEncoder :: Applicative parse => ValidEncoder parse (Maybe a, [a]) [a] --TODO: Really shouldn't *always* have the [a], even in the Nothing case
444+consEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Maybe a, [a]) [a] --TODO: Really shouldn't *always* have the [a], even in the Nothing case
317−consValidEncoder = ValidEncoder
445+consEncoder = unsafeMkEncoder $ EncoderImpl
318− { _validEncoder_encode = \(h, t) -> maybeToList h <> t
446+ { _encoderImpl_encode = \(h, t) -> maybeToList h <> t
319− , _validEncoder_decode = pure . \case
447+ , _encoderImpl_decode = pure . \case
320 [] -> (Nothing, [])
448 [] -> (Nothing, [])
321 h:t -> (Just h, t)
449 h:t -> (Just h, t)
322 }
450 }
@@ -336,31 +464,31 @@ shadowEncoder
336 -> Encoder check parse b c -- ^ Gets overlapped
464 -> Encoder check parse b c -- ^ Gets overlapped
337 -> Encoder check parse (Either a b) c
465 -> Encoder check parse (Either a b) c
338shadowEncoder f g = Encoder $ do
466shadowEncoder f g = Encoder $ do
339− vf <- checkEncoder f
467+ vf <- unEncoder f
340− vg <- checkEncoder g
468+ vg <- unEncoder g
341− let gCanParse c = catchError (Just <$> _validEncoder_decode vg c) (\_ -> pure Nothing)
469+ let gCanParse c = catchError (Just <$> _encoderImpl_decode vg c) (\_ -> pure Nothing)
342 overlaps <- fmap catMaybes $ forM universe $ \a -> do
470 overlaps <- fmap catMaybes $ forM universe $ \a -> do
343− let c = _validEncoder_encode vf a
471+ let c = _encoderImpl_encode vf a
344 mb <- gCanParse c
472 mb <- gCanParse c
345 pure $ fmap (\b -> (a, b, c)) mb
473 pure $ fmap (\b -> (a, b, c)) mb
346 case overlaps of
474 case overlaps of
347 [] -> pure ()
475 [] -> pure ()
348 _ -> throwError $ "shadowEncoder: overlap detected: " <> T.unlines
476 _ -> throwError $ "shadowEncoder: overlap detected: " <> T.unlines
349 (flip fmap overlaps $ \(a, b, c) -> "first encoder encodes " <> tshow a <> " as " <> tshow c <> ", which second encoder decodes as " <> tshow b)
477 (flip fmap overlaps $ \(a, b, c) -> "first encoder encodes " <> tshow a <> " as " <> tshow c <> ", which second encoder decodes as " <> tshow b)
350− pure $ ValidEncoder
478+ pure $ EncoderImpl
351− { _validEncoder_encode = \case
479+ { _encoderImpl_encode = \case
352− Left a -> _validEncoder_encode vf a
480+ Left a -> _encoderImpl_encode vf a
353− Right b -> _validEncoder_encode vg b
481+ Right b -> _encoderImpl_encode vg b
354− , _validEncoder_decode = \c -> (Left <$> _validEncoder_decode vf c) `catchError` \_ -> Right <$> _validEncoder_decode vg c
482+ , _encoderImpl_decode = \c -> (Left <$> _encoderImpl_decode vf c) `catchError` \_ -> Right <$> _encoderImpl_decode vg c
355 }
483 }
356
484
357enum1Encoder
485enum1Encoder
358 :: ( Universe (Some p)
486 :: ( Universe (Some p)
359 , GShow p
487 , GShow p
360 , GCompare p
488 , GCompare p
361− , Ord r
362 , MonadError Text check
489 , MonadError Text check
363 , MonadError Text parse
490 , MonadError Text parse
491+ , Ord r
364 , Show r
492 , Show r
365 )
493 )
366 => (forall a. p a -> r) -> Encoder check parse (Some p) r
494 => (forall a. p a -> r) -> Encoder check parse (Some p) r
@@ -381,40 +509,40 @@ enumEncoder f = Encoder $ do
381 case itraverse checkSingleton reversed :: Validation (Map r (Set p)) (Map r p) of
509 case itraverse checkSingleton reversed :: Validation (Map r (Set p)) (Map r p) of
382 Failure ambiguousEntries -> throwError $ T.unlines $
510 Failure ambiguousEntries -> throwError $ T.unlines $
383 "enumEncoder: ambiguous encodings detected:" : concat (Map.elems $ imap showRedundant ambiguousEntries)
511 "enumEncoder: ambiguous encodings detected:" : concat (Map.elems $ imap showRedundant ambiguousEntries)
384− Success m -> pure $ ValidEncoder
512+ Success m -> pure $ EncoderImpl
385− { _validEncoder_decode = \r -> case Map.lookup r m of
513+ { _encoderImpl_decode = \r -> case Map.lookup r m of
386 Just a -> pure a
514 Just a -> pure a
387 Nothing -> throwError $ "enumEncoder: not recognized: " <> tshow r --TODO: Report this as a better type
515 Nothing -> throwError $ "enumEncoder: not recognized: " <> tshow r --TODO: Report this as a better type
388− , _validEncoder_encode = f
516+ , _encoderImpl_encode = f
389 }
517 }
390
518
391−endValidEncoder :: (MonadError Text parse, Show r, Eq r) => r -> ValidEncoder parse () r
519+unitEncoder :: (Applicative check, MonadError Text parse, Show r, Eq r) => r -> Encoder check parse () r
392−endValidEncoder expected = ValidEncoder
520+unitEncoder expected = unsafeMkEncoder $ EncoderImpl
393− { _validEncoder_decode = \obtained ->
521+ { _encoderImpl_decode = \obtained ->
394 if obtained == expected
522 if obtained == expected
395 then pure ()
523 then pure ()
396− else throwError $ "endValidEncoder: expected " <> tshow expected <> ", got " <> tshow obtained
524+ else throwError $ "endEncoderImpl: expected " <> tshow expected <> ", got " <> tshow obtained
397− , _validEncoder_encode = \_ -> expected
525+ , _encoderImpl_encode = \_ -> expected
398 }
526 }
399
527
400−singlePathSegmentValidEncoder :: MonadError Text parse => ValidEncoder parse Text PageName
528+singlePathSegmentEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse Text PageName
401−singlePathSegmentValidEncoder = pathOnlyValidEncoder . singletonListValidEncoder
529+singlePathSegmentEncoder = pathOnlyEncoder . singletonListEncoder
402
530
403−pathOnlyValidEncoder :: (MonadError Text parse) => ValidEncoder parse [Text] PageName
531+pathOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse [Text] PageName
404−pathOnlyValidEncoder = ValidEncoder
532+pathOnlyEncoder = unsafeMkEncoder $ EncoderImpl
405− { _validEncoder_decode = \(path, query) ->
533+ { _encoderImpl_decode = \(path, query) ->
406 if query == mempty
534 if query == mempty
407 then pure path
535 then pure path
408− else throwError "pathOnlyValidEncoder: query was provided"
536+ else throwError "pathOnlyEncoderImpl: query was provided"
409− , _validEncoder_encode = \path -> (path, mempty)
537+ , _encoderImpl_encode = \path -> (path, mempty)
410 }
538 }
411
539
412−singletonListValidEncoder :: (MonadError Text parse) => ValidEncoder parse a [a]
540+singletonListEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a [a]
413−singletonListValidEncoder = ValidEncoder
541+singletonListEncoder = unsafeMkEncoder $ EncoderImpl
414− { _validEncoder_decode = \case
542+ { _encoderImpl_decode = \case
415 [a] -> pure a
543 [a] -> pure a
416− l -> throwError $ "singletonListValidEncoder: expected one item, got " <> tshow (length l)
544+ l -> throwError $ "singletonListEncoderImpl: expected one item, got " <> tshow (length l)
417− , _validEncoder_encode = (:[])
545+ , _encoderImpl_encode = (:[])
418 }
546 }
419
547
420splitTextNonEmpty :: Text -> Text -> NonEmpty Text
548splitTextNonEmpty :: Text -> Text -> NonEmpty Text
@@ -423,18 +551,18 @@ splitTextNonEmpty separator v = case T.splitOn separator v of
423 h : t -> h :| t
551 h : t -> h :| t
424
552
425--TODO: To know this is reversible, we must know that the separator isn't included anywhere in the input text
553--TODO: To know this is reversible, we must know that the separator isn't included anywhere in the input text
426−pathSegmentsTextEncoder :: (MonadError Text check, Applicative parse) => Encoder check parse (NonEmpty Text) Text
554+pathSegmentsTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse (NonEmpty Text) Text
427−pathSegmentsTextEncoder = Encoder $ pure $ ValidEncoder
555+pathSegmentsTextEncoder = unsafeMkEncoder $ EncoderImpl
428− { _validEncoder_encode = T.intercalate "/" . fmap (urlEncodeText False) . toList
556+ { _encoderImpl_encode = T.intercalate "/" . fmap (urlEncodeText False) . toList
429− , _validEncoder_decode = pure . fmap (urlDecodeText False) . splitTextNonEmpty "/"
557+ , _encoderImpl_decode = pure . fmap (urlDecodeText False) . splitTextNonEmpty "/"
430 }
558 }
431
559
432queryParametersTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse [(Text, Maybe Text)] Text
560queryParametersTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse [(Text, Maybe Text)] Text
433−queryParametersTextEncoder = Encoder $ pure $ ValidEncoder
561+queryParametersTextEncoder = Encoder $ pure $ EncoderImpl
434− { _validEncoder_encode = \case
562+ { _encoderImpl_encode = \case
435 [] -> ""
563 [] -> ""
436 params -> T.intercalate "&" (fmap encodeParameter params)
564 params -> T.intercalate "&" (fmap encodeParameter params)
437− , _validEncoder_decode = pure . \case
565+ , _encoderImpl_decode = pure . \case
438 "" -> []
566 "" -> []
439 encoded ->
567 encoded ->
440 let h :| t = splitTextNonEmpty "&" encoded
568 let h :| t = splitTextNonEmpty "&" encoded
@@ -456,30 +584,30 @@ urlDecodeText :: Bool -> Text -> Text
456urlDecodeText q = decodeUtf8 . urlDecode q . encodeUtf8
584urlDecodeText q = decodeUtf8 . urlDecode q . encodeUtf8
457
585
458listToNonEmptyEncoder :: (Applicative check, Applicative parse, Monoid a, Eq a) => Encoder check parse [a] (NonEmpty a)
586listToNonEmptyEncoder :: (Applicative check, Applicative parse, Monoid a, Eq a) => Encoder check parse [a] (NonEmpty a)
459−listToNonEmptyEncoder = Encoder $ pure $ ValidEncoder
587+listToNonEmptyEncoder = Encoder $ pure $ EncoderImpl
460− { _validEncoder_encode = \case
588+ { _encoderImpl_encode = \case
461 [] -> mempty :| []
589 [] -> mempty :| []
462 h : t -> h :| t
590 h : t -> h :| t
463− , _validEncoder_decode = \(h :| t) -> pure $
591+ , _encoderImpl_decode = \(h :| t) -> pure $
464 if h == mempty
592 if h == mempty
465 then []
593 then []
466 else h : t
594 else h : t
467 }
595 }
468
596
469prefixTextEncoder :: (Applicative check, MonadError Text parse) => Text -> Encoder check parse Text Text
597prefixTextEncoder :: (Applicative check, MonadError Text parse) => Text -> Encoder check parse Text Text
470−prefixTextEncoder p = Encoder $ pure $ ValidEncoder
598+prefixTextEncoder p = Encoder $ pure $ EncoderImpl
471− { _validEncoder_encode = mappend p
599+ { _encoderImpl_encode = mappend p
472− , _validEncoder_decode = \v -> case T.stripPrefix p v of
600+ , _encoderImpl_decode = \v -> case T.stripPrefix p v of
473 Nothing -> throwError $ "prefixTextEncoder: wrong prefix; expected " <> tshow p <> ", got " <> tshow (T.take (T.length p) v)
601 Nothing -> throwError $ "prefixTextEncoder: wrong prefix; expected " <> tshow p <> ", got " <> tshow (T.take (T.length p) v)
474 Just stripped -> pure stripped
602 Just stripped -> pure stripped
475 }
603 }
476
604
477prefixNonemptyTextEncoder :: (Applicative check, MonadError Text parse) => Text -> Encoder check parse Text Text
605prefixNonemptyTextEncoder :: (Applicative check, MonadError Text parse) => Text -> Encoder check parse Text Text
478−prefixNonemptyTextEncoder p = Encoder $ pure $ ValidEncoder
606+prefixNonemptyTextEncoder p = Encoder $ pure $ EncoderImpl
479− { _validEncoder_encode = \case
607+ { _encoderImpl_encode = \case
480 "" -> ""
608 "" -> ""
481 v -> p <> v
609 v -> p <> v
482− , _validEncoder_decode = \case
610+ , _encoderImpl_decode = \case
483 "" -> pure ""
611 "" -> pure ""
484 v -> case T.stripPrefix p v of
612 v -> case T.stripPrefix p v of
485 Nothing -> throwError $ "prefixTextEncoder: wrong prefix; expected " <> tshow p
613 Nothing -> throwError $ "prefixTextEncoder: wrong prefix; expected " <> tshow p
@@ -487,23 +615,23 @@ prefixNonemptyTextEncoder p = Encoder $ pure $ ValidEncoder
487 }
615 }
488
616
489unpackTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse Text String
617unpackTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse Text String
490−unpackTextEncoder = Encoder $ pure $ ValidEncoder
618+unpackTextEncoder = Encoder $ pure $ EncoderImpl
491− { _validEncoder_encode = T.unpack
619+ { _encoderImpl_encode = T.unpack
492− , _validEncoder_decode = pure . T.pack
620+ , _encoderImpl_decode = pure . T.pack
493 }
621 }
494
622
495toListMapEncoder :: (Applicative check, Applicative parse, Ord k) => Encoder check parse (Map k v) [(k, v)]
623toListMapEncoder :: (Applicative check, Applicative parse, Ord k) => Encoder check parse (Map k v) [(k, v)]
496−toListMapEncoder = Encoder $ pure $ ValidEncoder
624+toListMapEncoder = Encoder $ pure $ EncoderImpl
497− { _validEncoder_encode = Map.toList
625+ { _encoderImpl_encode = Map.toList
498− , _validEncoder_decode = pure . Map.fromList --TODO: Should we be stricter about repeated keys?
626+ , _encoderImpl_decode = pure . Map.fromList --TODO: Should we be stricter about repeated keys?
499 }
627 }
500
628
501joinPairTextEncoder :: (MonadError Text check, MonadError Text parse) => Text -> Encoder check parse (Text, Text) Text
629joinPairTextEncoder :: (MonadError Text check, MonadError Text parse) => Text -> Encoder check parse (Text, Text) Text
502joinPairTextEncoder = Encoder . \case
630joinPairTextEncoder = Encoder . \case
503 "" -> throwError "joinPairTextEncoder: empty separator"
631 "" -> throwError "joinPairTextEncoder: empty separator"
504− separator -> pure $ ValidEncoder
632+ separator -> pure $ EncoderImpl
505− { _validEncoder_encode = \(k, v) -> k <> separator <> v
633+ { _encoderImpl_encode = \(k, v) -> k <> separator <> v
506− , _validEncoder_decode = \r ->
634+ , _encoderImpl_decode = \r ->
507 let (kt, vt) = T.breakOn separator r
635 let (kt, vt) = T.breakOn separator r
508 in case vt of
636 in case vt of
509 -- The separator was not found
637 -- The separator was not found
@@ -516,137 +644,123 @@ joinPairTextEncoder = Encoder . \case
516rPrism :: forall f g. (forall a. Prism' (f a) (g a)) -> Prism' (R f) (R g)
644rPrism :: forall f g. (forall a. Prism' (f a) (g a)) -> Prism' (R f) (R g)
517rPrism p = prism (\(g :/ x) -> g ^. re p :/ x) (\(f :/ x) -> bimap (:/ x) (:/ x) $ matching p f)
645rPrism p = prism (\(g :/ x) -> g ^. re p :/ x) (\(f :/ x) -> bimap (:/ x) (:/ x) $ matching p f)
518
646
519−-- An encoder that only works on the items available via the prism
647+-- | An encoder that only works on the items available via the prism. An error will be thrown in the parse monad
648+-- if the prism doesn't match.
520−prismValidEncoder :: MonadError Text parse => Prism' b a -> ValidEncoder parse a b
649+prismEncoder :: (Applicative check, MonadError Text parse) => Prism' b a -> Encoder check parse a b
521−prismValidEncoder p = ValidEncoder
650+prismEncoder p = unsafeMkEncoder $ EncoderImpl
522− { _validEncoder_encode = (^. re p)
651+ { _encoderImpl_encode = (^. re p)
523− , _validEncoder_decode = \r -> case r ^? p of
652+ , _encoderImpl_decode = \r -> case r ^? p of
524 Just a -> pure a
653 Just a -> pure a
525− Nothing -> throwError "prismValidEncoder: value is not present in the prism"
654+ Nothing -> throwError "prismEncoder: value is not present in the prism"
526 }
655 }
527
656
657+-- | A URL path and query string, in which trailing slashes don't matter in the path
658+-- and duplicate query parameters are not allowed. A final goal of encoders using this library
659+-- will frequently be to produce this.
660+type PageName = ([Text], Map Text (Maybe Text))
528
661
662+-- | A path (separated by slashes), and a query string.
663+type PathQuery = (String, String)
664+
529−-- | Encode a PageName into a path and query string, suitable for use in the
665+-- | Encode a PageName into a path and query string.
530−-- 'URI' type
531−pageNameValidEncoder :: MonadError Text parse => ValidEncoder parse PageName (String, String)
666+pageNameEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse PageName PathQuery
532−pageNameValidEncoder = ve
667+pageNameEncoder = bimap
533− where Right ve = checkEncoder $ bimap
534− (unpackTextEncoder . prefixTextEncoder "/" . pathSegmentsTextEncoder . listToNonEmptyEncoder)
668+ (unpackTextEncoder . prefixTextEncoder "/" . pathSegmentsTextEncoder . listToNonEmptyEncoder)
535− (unpackTextEncoder . prefixNonemptyTextEncoder "?" . queryParametersTextEncoder . toListMapEncoder)
669+ (unpackTextEncoder . prefixNonemptyTextEncoder "?" . queryParametersTextEncoder . toListMapEncoder)
536−
670+
537−catchValidEncoder :: (e -> a) -> ValidEncoder (Either e) a b -> ValidEncoder Identity a b
671+-- | Handle an error in parsing, for example, in order to redirect to a 404 page.
538−catchValidEncoder recover ve = ve
672+handleEncoder
539− { _validEncoder_decode = \a -> pure $ case _validEncoder_decode ve a of
673+ :: (Functor check)
674+ => (e -> a)
675+ -> Encoder check (Either e) a b
676+ -> Encoder check Identity a b
677+handleEncoder recover e = Encoder $ do
678+ i <- unEncoder e
679+ return $ i
680+ { _encoderImpl_decode = \a -> pure $ case _encoderImpl_decode i a of
540 Right r -> r
681 Right r -> r
541 Left err -> recover err
682 Left err -> recover err
542− }
683+ }
543
684
544--------------------------------------------------------------------------------
685--------------------------------------------------------------------------------
545-- Actual obelisk route info
686-- Actual obelisk route info
546--------------------------------------------------------------------------------
687--------------------------------------------------------------------------------
547
688
689+-- | A type which can represent Obelisk-specific resource routes, in addition to application specific routes which serve your
690+-- frontend.
548data ObeliskRoute :: (* -> *) -> * -> * where
691data ObeliskRoute :: (* -> *) -> * -> * where
549 -- We need to have the `f a` as an argument here, because otherwise we have no way to specifically check for overlap between us and the given encoder
692 -- We need to have the `f a` as an argument here, because otherwise we have no way to specifically check for overlap between us and the given encoder
550 ObeliskRoute_App :: f a -> ObeliskRoute f a
693 ObeliskRoute_App :: f a -> ObeliskRoute f a
551 ObeliskRoute_Resource :: ResourceRoute a -> ObeliskRoute f a
694 ObeliskRoute_Resource :: ResourceRoute a -> ObeliskRoute f a
552
695
696+instance Universe (Some f) => Universe (Some (ObeliskRoute f)) where
697+ universe = fmap (\(Some.This x) -> Some.This (ObeliskRoute_App x)) universe
698+ ++ fmap (\(Some.This x) -> Some.This (ObeliskRoute_Resource x)) universe
699+
700+instance GEq f => GEq (ObeliskRoute f) where
701+ geq (ObeliskRoute_App x) (ObeliskRoute_App y) = geq x y
702+ geq (ObeliskRoute_Resource x) (ObeliskRoute_Resource y) = geq x y
703+ geq _ _ = Nothing
704+
705+instance GCompare f => GCompare (ObeliskRoute f) where
706+ gcompare (ObeliskRoute_App x) (ObeliskRoute_App y) = gcompare x y
707+ gcompare (ObeliskRoute_Resource x) (ObeliskRoute_Resource y) = gcompare x y
708+ gcompare (ObeliskRoute_App _) (ObeliskRoute_Resource _) = GLT
709+ gcompare (ObeliskRoute_Resource _) (ObeliskRoute_App _) = GGT
710+
711+-- | A type representing the various resource routes served by Obelisk. These can in principle map to any physical routes you want,
712+-- but sane defaults are provided by 'resourceRouteSegment'
553data ResourceRoute :: * -> * where
713data ResourceRoute :: * -> * where
554 ResourceRoute_Static :: ResourceRoute [Text] -- This [Text] represents the *path in our static files directory*, not necessarily the URL path that the asset gets served at (although that will often be "/static/this/text/thing")
714 ResourceRoute_Static :: ResourceRoute [Text] -- This [Text] represents the *path in our static files directory*, not necessarily the URL path that the asset gets served at (although that will often be "/static/this/text/thing")
555 ResourceRoute_Ghcjs :: ResourceRoute [Text]
715 ResourceRoute_Ghcjs :: ResourceRoute [Text]
556 ResourceRoute_JSaddleWarp :: ResourceRoute (R JSaddleWarpRoute)
716 ResourceRoute_JSaddleWarp :: ResourceRoute (R JSaddleWarpRoute)
557
717
558−--TODO: Figure out a way to check this
718+-- | If there are no additional backend routes in your app (i.e. ObeliskRoute gives you all the routes you need),
559−obeliskComponentEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (ObeliskRoute f)) (Either (Some ResourceRoute) (Some f))
719+-- this constructs a suitable 'Encoder' to use for encoding routes to 'PageName's. If you do have additional backend routes,
560−obeliskComponentEncoder = Encoder $ pure $ ValidEncoder
720+-- you'll want to use 'pathComponentEncoder' yourself, applied to a function that will likely use obeliskRouteSegment in order to
561− { _validEncoder_encode = \(Some.This o) -> case o of
721+-- handle the ObeliskRoute case (i.e. Obelisk resource routes and app frontend routes).
562− ObeliskRoute_App r -> Right $ Some.This r
563− ObeliskRoute_Resource r -> Left $ Some.This r
564− , _validEncoder_decode = pure . \case
565− Right (Some.This r) -> Some.This $ ObeliskRoute_App r
566− Left (Some.This r) -> Some.This $ ObeliskRoute_Resource r
567− }
568−
569−newtype ValidEncoderFunc parse p r = ValidEncoderFunc { runValidEncoderFunc :: forall a. p a -> ValidEncoder parse a r }
570−
571−checkEnum1EncoderFunc
572− :: forall check parse p r.
573− ( Universe (Some p)
574− , GCompare p
575− , Monad check
576− )
577− => (forall a. p a -> Encoder check parse a r)
578− -> check (ValidEncoderFunc parse p r)
579−checkEnum1EncoderFunc f = do
580− validEncoders :: DMap p (Flip (ValidEncoder parse) r) <- DMap.fromList <$> traverse (\(Some.This p) -> (p :=>) . Flip <$> checkEncoder (f p)) universe
581− pure $ ValidEncoderFunc $ \p -> unFlip $ DMap.findWithDefault (error "checkEnum1EncoderFunc: ValidEncoder not found (should be impossible)") p validEncoders
582−
583−obeliskRouteEncoder
584− :: forall check parse appRoute.
722+obeliskRouteEncoder :: forall check parse appRoute.
585− ( Universe (Some appRoute)
723+ ( Universe (Some (ObeliskRoute appRoute))
586− , GCompare appRoute
724+ , GCompare (ObeliskRoute appRoute)
587 , GShow appRoute
725 , GShow appRoute
588 , MonadError Text check
726 , MonadError Text check
589 , check ~ parse --TODO: Get rid of this
727 , check ~ parse --TODO: Get rid of this
590 )
728 )
591− => (Encoder check parse (Some appRoute) (Maybe Text))
592− -> (forall a. appRoute a -> Encoder check parse a PageName)
729+ => (forall a. appRoute a -> SegmentResult check parse a)
593 -> Encoder check parse (R (ObeliskRoute appRoute)) PageName
730 -> Encoder check parse (R (ObeliskRoute appRoute)) PageName
594−obeliskRouteEncoder appComponentEncoder appRouteEncoder = Encoder $ do
731+obeliskRouteEncoder appRouteSegment = pathComponentEncoder $ \r ->
595− let componentEncoder = obeliskRouteComponentEncoder appComponentEncoder
732+ obeliskRouteSegment r appRouteSegment
596− restEncoder <- checkObeliskRouteRestEncoder appRouteEncoder
733+
597− checkEncoder $ pathComponentEncoder componentEncoder $ runValidEncoderFunc restEncoder
734+-- | From a function which explains how app-specific frontend routes translate into segments, produce a function which does the
598−
735+-- same for ObeliskRoute. This uses the given function for the 'ObeliskRoute_App' case, and 'resourceRouteSegment' for the
599−checkObeliskRouteRestEncoder
736+-- 'ObeliskRoute_Resource' case.
737+obeliskRouteSegment :: forall check parse appRoute a.
600− :: ( MonadError Text check
738+ (MonadError Text check, MonadError Text parse)
601− , MonadError Text parse
602− , Universe (Some appRoute)
603− , GCompare appRoute
739+ => ObeliskRoute appRoute a
604− )
605− => (forall a. appRoute a -> Encoder check parse a PageName)
740+ -> (forall b. appRoute b -> SegmentResult check parse b)
606− -> check (ValidEncoderFunc parse (ObeliskRoute appRoute) PageName)
741+ -> SegmentResult check parse a
607−checkObeliskRouteRestEncoder appRouteEncoder = do
742+obeliskRouteSegment r appRouteSegment = case r of
608− appRouteValidEncoder <- checkEnum1EncoderFunc appRouteEncoder
609− resourceRouteValidEncoder <- checkEnum1EncoderFunc resourceRouteEncoder
610− return $ ValidEncoderFunc $ \case
611− ObeliskRoute_App appRoute -> runValidEncoderFunc appRouteValidEncoder appRoute
743+ ObeliskRoute_App appRoute -> appRouteSegment appRoute
612− ObeliskRoute_Resource resRoute -> runValidEncoderFunc resourceRouteValidEncoder resRoute
744+ ObeliskRoute_Resource resourceRoute -> resourceRouteSegment resourceRoute
613−
745+
614−obeliskRouteComponentEncoder
746+-- | A function which gives a sane default for how to encode Obelisk resource routes. It's given in this form, because it will
615− :: ( MonadError Text parse
747+-- be combined with other such segment encoders before 'pathComponentEncoder' turns it into a proper 'Encoder'.
616− , GShow appRoute
748+resourceRouteSegment :: (MonadError Text check, MonadError Text parse) => ResourceRoute a -> SegmentResult check parse a
617− , check ~ parse
749+resourceRouteSegment = \case
618− )
619− => Encoder check parse (Some appRoute) (Maybe Text) -> Encoder check parse (Some (ObeliskRoute appRoute)) (Maybe Text)
620−obeliskRouteComponentEncoder appComponentEncoder = (resourceComponentEncoder `shadowEncoder` appComponentEncoder) . obeliskComponentEncoder
621−
622−resourceComponentEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (Some ResourceRoute) (Maybe Text)
623−resourceComponentEncoder = enum1Encoder $ \case
624− ResourceRoute_Static -> Just "static"
750+ ResourceRoute_Static -> PathSegment "static" $ pathOnlyEncoder
625− ResourceRoute_Ghcjs -> Just "ghcjs"
751+ ResourceRoute_Ghcjs -> PathSegment "ghcjs" $ pathOnlyEncoder
626− ResourceRoute_JSaddleWarp -> Just "jsaddle"
752+ ResourceRoute_JSaddleWarp -> PathSegment "jsaddle" $ jsaddleWarpRouteEncoder
627−
628−resourceRouteEncoder :: (MonadError Text check, MonadError Text parse) => ResourceRoute a -> Encoder check parse a PageName
629−resourceRouteEncoder = \case
630− ResourceRoute_Static -> Encoder $ pure pathOnlyValidEncoder
631− ResourceRoute_Ghcjs -> Encoder $ pure pathOnlyValidEncoder
632− ResourceRoute_JSaddleWarp -> jsaddleWarpRouteEncoder
633
753
634data JSaddleWarpRoute :: * -> * where
754data JSaddleWarpRoute :: * -> * where
635 JSaddleWarpRoute_JavaScript :: JSaddleWarpRoute ()
755 JSaddleWarpRoute_JavaScript :: JSaddleWarpRoute ()
636 JSaddleWarpRoute_WebSocket :: JSaddleWarpRoute ()
756 JSaddleWarpRoute_WebSocket :: JSaddleWarpRoute ()
637 JSaddleWarpRoute_Sync :: JSaddleWarpRoute [Text]
757 JSaddleWarpRoute_Sync :: JSaddleWarpRoute [Text]
638
758
639−jsaddleWarpRouteComponentEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (Some JSaddleWarpRoute) (Maybe Text)
640−jsaddleWarpRouteComponentEncoder = enum1Encoder $ \case
641− JSaddleWarpRoute_JavaScript -> Just "jsaddle.js"
642− JSaddleWarpRoute_WebSocket -> Nothing
643− JSaddleWarpRoute_Sync -> Just "sync"
644−
645jsaddleWarpRouteEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (R JSaddleWarpRoute) PageName
759jsaddleWarpRouteEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (R JSaddleWarpRoute) PageName
646−jsaddleWarpRouteEncoder = pathComponentEncoder jsaddleWarpRouteComponentEncoder $ \case
760+jsaddleWarpRouteEncoder = pathComponentEncoder $ \case
647− JSaddleWarpRoute_JavaScript -> endValidEncoder mempty
761+ JSaddleWarpRoute_JavaScript -> PathSegment "jsaddle.js" $ unitEncoder mempty
648− JSaddleWarpRoute_WebSocket -> endValidEncoder mempty
762+ JSaddleWarpRoute_WebSocket -> PathEnd $ unitEncoder mempty
649− JSaddleWarpRoute_Sync -> pathOnlyValidEncoder
763+ JSaddleWarpRoute_Sync -> PathSegment "sync" $ pathOnlyEncoder
650
764
651instance GShow appRoute => GShow (ObeliskRoute appRoute) where
765instance GShow appRoute => GShow (ObeliskRoute appRoute) where
652 gshowsPrec prec = \case
766 gshowsPrec prec = \case
@@ -655,32 +769,22 @@ instance GShow appRoute => GShow (ObeliskRoute appRoute) where
655 ObeliskRoute_Resource appRoute -> showParen (prec > 10) $
769 ObeliskRoute_Resource appRoute -> showParen (prec > 10) $
656 showString "ObeliskRoute_Resource " . gshowsPrec 11 appRoute
770 showString "ObeliskRoute_Resource " . gshowsPrec 11 appRoute
657
771
658−
659data IndexOnlyRoute :: * -> * where
772data IndexOnlyRoute :: * -> * where
660 IndexOnlyRoute :: IndexOnlyRoute ()
773 IndexOnlyRoute :: IndexOnlyRoute ()
661
774
662−indexOnlyRouteComponentEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (Some IndexOnlyRoute) (Maybe Text)
775+indexOnlyRouteSegment :: (Applicative check, MonadError Text parse) => IndexOnlyRoute a -> SegmentResult check parse a
663−indexOnlyRouteComponentEncoder = enum1Encoder $ \case
776+indexOnlyRouteSegment = \case
664− IndexOnlyRoute -> Nothing
777+ IndexOnlyRoute -> PathEnd $ unitEncoder mempty
665
778
666−indexOnlyRouteRestEncoder :: (Applicative check, MonadError Text parse) => IndexOnlyRoute a -> Encoder check parse a PageName
779+indexOnlyRouteEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (R IndexOnlyRoute) PageName
667−indexOnlyRouteRestEncoder = \case
780+indexOnlyRouteEncoder = pathComponentEncoder indexOnlyRouteSegment
668− IndexOnlyRoute -> Encoder $ pure $ constLaxValidEncoder mempty
669−
670−--TODO: Come up with a naming scheme that is better at describing how permissive
671−--versus strict things are
672−constLaxValidEncoder :: Applicative parse => a -> ValidEncoder parse () a
673−constLaxValidEncoder a = ValidEncoder
674− { _validEncoder_encode = \_ -> a
675− , _validEncoder_decode = \_ -> pure ()
676− }
677
781
678someSumEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (Sum a b)) (Either (Some a) (Some b))
782someSumEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (Sum a b)) (Either (Some a) (Some b))
679−someSumEncoder = Encoder $ pure $ ValidEncoder
783+someSumEncoder = Encoder $ pure $ EncoderImpl
680− { _validEncoder_encode = \(Some.This t) -> case t of
784+ { _encoderImpl_encode = \(Some.This t) -> case t of
681 InL l -> Left $ Some.This l
785 InL l -> Left $ Some.This l
682 InR r -> Right $ Some.This r
786 InR r -> Right $ Some.This r
683− , _validEncoder_decode = pure . \case
787+ , _encoderImpl_decode = pure . \case
684 Left (Some.This l) -> Some.This (InL l)
788 Left (Some.This l) -> Some.This (InL l)
685 Right (Some.This r) -> Some.This (InR r)
789 Right (Some.This r) -> Some.This (InR r)
686 }
790 }
@@ -691,23 +795,23 @@ instance Universe (Some Void1) where
691 universe = []
795 universe = []
692
796
693void1Encoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Some Void1) a
797void1Encoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Some Void1) a
694−void1Encoder = Encoder $ pure $ ValidEncoder
798+void1Encoder = Encoder $ pure $ EncoderImpl
695− { _validEncoder_encode = \case
799+ { _encoderImpl_encode = \case
696 Some.This f -> case f of {}
800 Some.This f -> case f of {}
697− , _validEncoder_decode = \_ -> throwError "void1Encoder: can't decode anything"
801+ , _encoderImpl_decode = \_ -> throwError "void1Encoder: can't decode anything"
698 }
802 }
699
803
700instance GShow Void1 where
804instance GShow Void1 where
701 gshowsPrec _ = \case {}
805 gshowsPrec _ = \case {}
702
806
703−makePrisms ''ObeliskRoute
704−
705concat <$> mapM deriveRouteComponent
807concat <$> mapM deriveRouteComponent
706 [ ''ResourceRoute
808 [ ''ResourceRoute
707 , ''JSaddleWarpRoute
809 , ''JSaddleWarpRoute
708 , ''IndexOnlyRoute
810 , ''IndexOnlyRoute
709 ]
811 ]
710
812
813+makePrisms ''ObeliskRoute
814+
711deriveGEq ''Void1
815deriveGEq ''Void1
712deriveGCompare ''Void1
816deriveGCompare ''Void1
713
817
lib/route/src/Obelisk/Route/Frontend.hs+141−15
@@ -19,6 +19,9 @@
19{-# LANGUAGE ViewPatterns #-}
19{-# LANGUAGE ViewPatterns #-}
20{-# LANGUAGE GeneralizedNewtypeDeriving #-}
20{-# LANGUAGE GeneralizedNewtypeDeriving #-}
21{-# LANGUAGE UndecidableInstances #-}
21{-# LANGUAGE UndecidableInstances #-}
22+{-# LANGUAGE DataKinds #-}
23+{-# LANGUAGE FunctionalDependencies #-}
24+
22module Obelisk.Route.Frontend
25module Obelisk.Route.Frontend
23 ( module Obelisk.Route
26 ( module Obelisk.Route
24 , pattern (:~)
27 , pattern (:~)
@@ -30,7 +33,14 @@ module Obelisk.Route.Frontend
30 , mapRoutedT
33 , mapRoutedT
31 , subRoute
34 , subRoute
32 , subRoute_
35 , subRoute_
36+ , maybeRoute
37+ , maybeRoute_
38+ , maybeRouted
33 , runRouteViewT
39 , runRouteViewT
40+ , SetRouteT(..)
41+ , SetRoute(..)
42+ , runSetRouteT
43+ , mapSetRouteT
34 ) where
44 ) where
35
45
36import Prelude hiding ((.), id)
46import Prelude hiding ((.), id)
@@ -51,7 +61,7 @@ import Data.Constraint (Dict (..))
51import Data.Dependent.Sum (DSum (..))
61import Data.Dependent.Sum (DSum (..))
52import Data.GADT.Compare
62import Data.GADT.Compare
53import Data.Monoid
63import Data.Monoid
54−import Data.Text (Text)
64+import qualified Data.Text as T
55import Data.Functor.Compose
65import Data.Functor.Compose
56import Reflex.Class
66import Reflex.Class
57import Reflex.Host.Class
67import Reflex.Host.Class
@@ -67,6 +77,10 @@ import Language.Javascript.JSaddle --TODO: Get rid of this - other platforms can
67import Reflex.Dom.Core
77import Reflex.Dom.Core
68import qualified GHCJS.DOM.Types as DOM
78import qualified GHCJS.DOM.Types as DOM
69import Network.URI
79import Network.URI
80+#if defined(ios_HOST_OS)
81+import Data.Maybe (fromMaybe)
82+import qualified Data.List as L
83+#endif
70
84
71import Unsafe.Coerce
85import Unsafe.Coerce
72
86
@@ -74,7 +88,7 @@ infixr 5 :~
74pattern (:~) :: Reflex t => f a -> Dynamic t a -> DSum f (Compose (Dynamic t) Identity)
88pattern (:~) :: Reflex t => f a -> Dynamic t a -> DSum f (Compose (Dynamic t) Identity)
75pattern a :~ b <- a :=> (coerceDynamic . getCompose -> b)
89pattern a :~ b <- a :=> (coerceDynamic . getCompose -> b)
76
90
77−class Routed t r m where
91+class Routed t r m | m -> t r where
78 askRoute :: m (Dynamic t r)
92 askRoute :: m (Dynamic t r)
79
93
80instance Monad m => Routed t r (RoutedT t r m) where
94instance Monad m => Routed t r (RoutedT t r m) where
@@ -153,6 +167,22 @@ subRoute :: (MonadFix m, MonadHold t m, GEq r, Adjustable t m) => (forall a. r a
153subRoute f = factorRouted $ strictDynWidget $ \(c :=> r') -> do
167subRoute f = factorRouted $ strictDynWidget $ \(c :=> r') -> do
154 runRoutedT (f c) r'
168 runRoutedT (f c) r'
155
169
170+maybeRoute_ :: (MonadFix m, MonadHold t m, Adjustable t m) => m () -> RoutedT t r m () -> RoutedT t (Maybe r) m ()
171+maybeRoute_ n j = maybeRouted $ strictDynWidget_ $ \case
172+ Nothing -> n
173+ Just r -> runRoutedT j r
174+
175+maybeRoute :: (MonadFix m, MonadHold t m, Adjustable t m) => m a -> RoutedT t r m a -> RoutedT t (Maybe r) m (Dynamic t a)
176+maybeRoute n j = maybeRouted $ strictDynWidget $ \case
177+ Nothing -> n
178+ Just r -> runRoutedT j r
179+
180+{-
181+maybeRoute :: (MonadFix m, MonadHold t m, GEq r, Adjustable t m) => m a -> RoutedT t r m a -> RoutedT t (Maybe r) m a
182+maybeRoute f = factorRouted $ strictDynWidget $ \(c :=> r') -> do
183+ runRoutedT (f c) r'
184+-}
185+
156dsumValueCoercion :: Coercion f g -> Coercion (DSum k f) (DSum k g)
186dsumValueCoercion :: Coercion f g -> Coercion (DSum k f) (DSum k g)
157dsumValueCoercion Coercion = Coercion
187dsumValueCoercion Coercion = Coercion
158
188
@@ -164,6 +194,11 @@ factorRouted r = RoutedT $ ReaderT $ \d -> do
164 d' <- factorDyn d
194 d' <- factorDyn d
165 runRoutedT r $ (coerceWith (dynamicCoercion $ dsumValueCoercion dynamicIdentityCoercion) d')
195 runRoutedT r $ (coerceWith (dynamicCoercion $ dsumValueCoercion dynamicIdentityCoercion) d')
166
196
197+maybeRouted :: (Reflex t, MonadFix m, MonadHold t m) => RoutedT t (Maybe (Dynamic t a)) m b -> RoutedT t (Maybe a) m b
198+maybeRouted r = RoutedT $ ReaderT $ \d -> do
199+ d' <- maybeDyn d
200+ runRoutedT r d'
201+
167-- | WARNING: The input 'Dynamic' must be fully constructed when this is run
202-- | WARNING: The input 'Dynamic' must be fully constructed when this is run
168strictDynWidget :: (MonadSample t m, MonadHold t m, Adjustable t m) => (a -> m b) -> RoutedT t a m (Dynamic t b)
203strictDynWidget :: (MonadSample t m, MonadHold t m, Adjustable t m) => (a -> m b) -> RoutedT t a m (Dynamic t b)
169strictDynWidget f = RoutedT $ ReaderT $ \r -> do
204strictDynWidget f = RoutedT $ ReaderT $ \r -> do
@@ -177,6 +212,69 @@ strictDynWidget_ f = RoutedT $ ReaderT $ \r -> do
177 (_, _) <- runWithReplace (f r0) $ f <$> updated r
212 (_, _) <- runWithReplace (f r0) $ f <$> updated r
178 pure ()
213 pure ()
179
214
215+newtype SetRouteT t r m a = SetRouteT { unSetRouteT :: EventWriterT t (Endo r) m a }
216+ deriving (Functor, Applicative, Monad, MonadFix, MonadTrans, NotReady t, MonadHold t, MonadSample t, PostBuild t, TriggerEvent t, HasJSContext, MonadIO, MonadReflexCreateTrigger t, HasDocument, DomBuilder t)
217+
218+mapSetRouteT :: (forall x. m x -> n x) -> SetRouteT t r m a -> SetRouteT t r n a
219+mapSetRouteT f (SetRouteT x) = SetRouteT (mapEventWriterT f x)
220+
221+runSetRouteT :: (Reflex t, Monad m) => SetRouteT t r m a -> m (a, Event t (Endo r))
222+runSetRouteT = runEventWriterT . unSetRouteT
223+
224+class Reflex t => SetRoute t r m | m -> t r where
225+ setRoute :: Event t r -> m ()
226+ modifyRoute :: Event t (r -> r) -> m ()
227+ setRoute = modifyRoute . fmap const
228+
229+instance (Reflex t, Monad m) => SetRoute t r (SetRouteT t r m) where
230+ modifyRoute = SetRouteT . tellEvent . fmap Endo
231+
232+instance (Monad m, SetRoute t r m) => SetRoute t r (RoutedT t r' m) where
233+ modifyRoute = lift . modifyRoute
234+
235+instance Prerender js m => Prerender js (SetRouteT t r m) where
236+ prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
237+
238+instance Requester t m => Requester t (SetRouteT t r m) where
239+ type Request (SetRouteT t r m) = Request m
240+ type Response (SetRouteT t r m) = Response m
241+ requesting = SetRouteT . requesting
242+ requesting_ = SetRouteT . requesting_
243+
244+#ifndef ghcjs_HOST_OS
245+deriving instance MonadJSM m => MonadJSM (SetRouteT t r m)
246+#endif
247+
248+instance PerformEvent t m => PerformEvent t (SetRouteT t r m) where
249+ type Performable (SetRouteT t r m) = Performable m
250+ performEvent = lift . performEvent
251+ performEvent_ = lift . performEvent_
252+
253+instance MonadRef m => MonadRef (SetRouteT t r m) where
254+ type Ref (SetRouteT t r m) = Ref m
255+ newRef = lift . newRef
256+ readRef = lift . readRef
257+ writeRef r = lift . writeRef r
258+
259+instance HasJS x m => HasJS x (SetRouteT t r m) where
260+ type JSX (SetRouteT t r m) = JSX m
261+ liftJS = lift . liftJS
262+
263+instance PrimMonad m => PrimMonad (SetRouteT t r m ) where
264+ type PrimState (SetRouteT t r m) = PrimState m
265+ primitive = lift . primitive
266+
267+instance (MonadHold t m, Adjustable t m) => Adjustable t (SetRouteT t r m) where
268+ runWithReplace a0 a' = SetRouteT $ runWithReplace (coerce a0) $ coerceEvent a'
269+ traverseIntMapWithKeyWithAdjust f a0 a' = SetRouteT $ traverseIntMapWithKeyWithAdjust (coerce f) (coerce a0) $ coerce a'
270+ traverseDMapWithKeyWithAdjust f a0 a' = SetRouteT $ traverseDMapWithKeyWithAdjust (\k v -> coerce $ f k v) (coerce a0) $ coerce a'
271+ traverseDMapWithKeyWithAdjustWithMove f a0 a' = SetRouteT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> coerce $ f k v) (coerce a0) $ coerce a'
272+
273+instance (Monad m, MonadQuery t vs m) => MonadQuery t vs (SetRouteT t r m) where
274+ tellQueryIncremental = lift . tellQueryIncremental
275+ askQueryResult = lift askQueryResult
276+ queryIncremental = lift . queryIncremental
277+
180runRouteViewT
278runRouteViewT
181 :: forall t m r a.
279 :: forall t m r a.
182 ( TriggerEvent t m
280 ( TriggerEvent t m
@@ -186,18 +284,24 @@ runRouteViewT
186 , MonadJSM (Performable m)
284 , MonadJSM (Performable m)
187 , MonadFix m
285 , MonadFix m
188 )
286 )
189− => (ValidEncoder (Either Text) r PageName)
287+ => (Encoder Identity Identity r PageName)
190− -> (Text -> r) -- ^ 404 page
191− -> RoutedT t r (EventWriterT t (Endo r) m) a
288+ -> RoutedT t r (SetRouteT t r m) a
192 -> m a
289 -> m a
193−runRouteViewT routeValidEncoder error404 a = do
290+runRouteViewT routeEncoder a = do
194 rec historyState <- manageHistory $ HistoryCommand_PushState <$> setState
291 rec historyState <- manageHistory $ HistoryCommand_PushState <$> setState
292+ let theEncoder = pageNameEncoder . hoistParse (pure . runIdentity) routeEncoder
293+ -- NB: The can only fail if the uriPath doesn't begin with a '/' or if the uriQuery
294+ -- is nonempty, but begins with a character that isn't '?'. Since we don't expect
295+ -- this ever to happen, we'll just handle it by failing completely with 'error'.
195− let route :: Dynamic t r
296+ route :: Dynamic t r
196− route = fmap (runIdentity . _validEncoder_decode (catchValidEncoder error404 $ pageNameValidEncoder . routeValidEncoder) . (uriPath &&& uriQuery) . _historyItem_uri) historyState
297+ route = fmap (errorLeft . tryDecode theEncoder . (adaptedUriPath &&& uriQuery) . _historyItem_uri) historyState
298+ where
299+ errorLeft (Left e) = error (T.unpack e)
300+ errorLeft (Right x) = x
197− (result, changeState) <- runEventWriterT $ runRoutedT a route
301+ (result, changeState) <- runSetRouteT $ runRoutedT a route
198− let f oldRoute change =
302+ let f (currentHistoryState, oldRoute) change =
199 let newRoute = appEndo change oldRoute
303 let newRoute = appEndo change oldRoute
200− (newPath, newQuery) = _validEncoder_encode (pageNameValidEncoder . routeValidEncoder) newRoute
304+ (newPath, newQuery) = encode theEncoder newRoute
201 in HistoryStateUpdate
305 in HistoryStateUpdate
202 { _historyStateUpdate_state = DOM.SerializedScriptValue jsNull
306 { _historyStateUpdate_state = DOM.SerializedScriptValue jsNull
203 -- We always provide "" as the title. On Firefox, Chrome, and
307 -- We always provide "" as the title. On Firefox, Chrome, and
@@ -211,10 +315,32 @@ runRouteViewT routeValidEncoder error404 a = do
211 -- we can change this function later to accommodate.
315 -- we can change this function later to accommodate.
212 -- See: https://github.com/whatwg/html/issues/2174
316 -- See: https://github.com/whatwg/html/issues/2174
213 , _historyStateUpdate_title = ""
317 , _historyStateUpdate_title = ""
214− , _historyStateUpdate_uri = Just $ nullURI
318+ , _historyStateUpdate_uri = Just $ setAdaptedUriPath newPath $ (_historyItem_uri currentHistoryState)
215− { uriPath = newPath
216− , uriQuery = newQuery
319+ { uriQuery = newQuery
217 }
320 }
218 }
321 }
219− setState = attachWith f (current route) changeState
322+ setState = attachWith f ((,) <$> current historyState <*> current route) changeState
220 return result
323 return result
324+
325+-- On ios due to sandboxing when loading the page from a file adapt the
326+-- path to be based on the hash.
327+
328+adaptedUriPath :: URI -> String
329+#if defined(ios_HOST_OS)
330+adaptedUriPath = hashToPath . uriFragment
331+
332+hashToPath :: String -> String
333+hashToPath = ('/' :) . fromMaybe "" . L.stripPrefix "#"
334+#else
335+adaptedUriPath = uriPath
336+#endif
337+
338+setAdaptedUriPath :: String -> URI -> URI
339+#if defined(ios_HOST_OS)
340+setAdaptedUriPath s u = u { uriFragment = pathToHash s }
341+
342+pathToHash :: String -> String
343+pathToHash = ('#' :) . fromMaybe "" . L.stripPrefix "/"
344+#else
345+setAdaptedUriPath s u = u { uriPath = s }
346+#endif
lib/run/obelisk-run.cabal+1
@@ -22,6 +22,7 @@ library
22 , modern-uri
22 , modern-uri
23 , mtl
23 , mtl
24 , network
24 , network
25+ , obelisk-asset-serve-snap
25 , obelisk-backend
26 , obelisk-backend
26 , obelisk-executable-config
27 , obelisk-executable-config
27 , obelisk-executable-config-inject
28 , obelisk-executable-config-inject
lib/run/src/Obelisk/Run.hs+16−14
@@ -26,7 +26,6 @@ import Control.Lens ((%~), (^?), _Just, _Right)
26import qualified Data.Attoparsec.ByteString.Char8 as A
26import qualified Data.Attoparsec.ByteString.Char8 as A
27import Data.ByteString (ByteString)
27import Data.ByteString (ByteString)
28import qualified Data.ByteString.Char8 as BSC
28import qualified Data.ByteString.Char8 as BSC
29−import qualified Data.ByteString.Lazy as LBS
30import qualified Data.ByteString.Lazy.Char8 as BSLC
29import qualified Data.ByteString.Lazy.Char8 as BSLC
31import Data.Dependent.Sum (DSum (..))
30import Data.Dependent.Sum (DSum (..))
32import Data.Functor.Identity
31import Data.Functor.Identity
@@ -37,7 +36,6 @@ import Data.Semigroup ((<>))
37import Data.Streaming.Network (bindPortTCP)
36import Data.Streaming.Network (bindPortTCP)
38import Data.Text (Text)
37import Data.Text (Text)
39import qualified Data.Text as T
38import qualified Data.Text as T
40−import Data.Text.Encoding
41import Language.Javascript.JSaddle.Run (syncPoint)
39import Language.Javascript.JSaddle.Run (syncPoint)
42import Language.Javascript.JSaddle.WebSockets
40import Language.Javascript.JSaddle.WebSockets
43import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)
41import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)
@@ -50,26 +48,28 @@ import Network.Wai.Handler.Warp
50import Network.Wai.Handler.Warp.Internal (settingsHost, settingsPort)
48import Network.Wai.Handler.Warp.Internal (settingsHost, settingsPort)
51import Network.WebSockets (ConnectionOptions)
49import Network.WebSockets (ConnectionOptions)
52import Network.WebSockets.Connection (defaultConnectionOptions)
50import Network.WebSockets.Connection (defaultConnectionOptions)
51+import qualified Obelisk.Asset.Serve.Snap as Snap
53import Obelisk.ExecutableConfig (get)
52import Obelisk.ExecutableConfig (get)
54import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
53import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
55import Obelisk.Frontend
54import Obelisk.Frontend
56import Obelisk.Route.Frontend
55import Obelisk.Route.Frontend
57import Reflex.Dom.Core
56import Reflex.Dom.Core
57+import Snap.Core (Snap)
58import System.Environment
58import System.Environment
59import System.IO
59import System.IO
60import System.Process
60import System.Process
61import Text.URI (URI)
61import Text.URI (URI)
62import qualified Text.URI as URI
62import qualified Text.URI as URI
63import Text.URI.Lens
63import Text.URI.Lens
64−import Snap.Core (writeText)
65import Obelisk.Backend
64import Obelisk.Backend
66
65
67run
66run
68 :: Int -- ^ Port to run the backend
67 :: Int -- ^ Port to run the backend
68+ -> ([Text] -> Snap ()) -- ^ Static asset handler
69 -> Backend fullRoute frontendRoute -- ^ Backend
69 -> Backend fullRoute frontendRoute -- ^ Backend
70 -> Frontend (R frontendRoute) -- ^ Frontend
70 -> Frontend (R frontendRoute) -- ^ Frontend
71 -> IO ()
71 -> IO ()
72−run port backend frontend = do
72+run port serveStaticAsset backend frontend = do
73 prettifyOutput
73 prettifyOutput
74 let handleBackendErr (e :: IOException) = hPutStrLn stderr $ "backend stopped; make a change to your code to reload - error " <> show e
74 let handleBackendErr (e :: IOException) = hPutStrLn stderr $ "backend stopped; make a change to your code to reload - error " <> show e
75 --TODO: Use Obelisk.Backend.runBackend; this will require separating the checking and running phases
75 --TODO: Use Obelisk.Backend.runBackend; this will require separating the checking and running phases
@@ -80,13 +80,16 @@ run port backend frontend = do
80 _backend_run backend $ \serveRoute -> do
80 _backend_run backend $ \serveRoute -> do
81 runSnapWithCommandLineArgs $ do
81 runSnapWithCommandLineArgs $ do
82 getRouteWith validFullEncoder >>= \case
82 getRouteWith validFullEncoder >>= \case
83− Left e -> writeText e
84− Right r -> case r of
83+ Identity r -> case r of
85 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
84 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
86− InR obeliskRoute :=> Identity a -> serveDefaultObeliskApp frontend $ obeliskRoute :/ a
85+ InR obeliskRoute :=> Identity a -> serveDefaultObeliskApp serveStaticAsset frontend $ obeliskRoute :/ a
87 let conf = defRunConfig { _runConfig_redirectPort = port }
86 let conf = defRunConfig { _runConfig_redirectPort = port }
88 runWidget conf frontend validFullEncoder `finally` killThread backendTid
87 runWidget conf frontend validFullEncoder `finally` killThread backendTid
89
88
89+-- Convenience wrapper to handle path segments for 'Snap.serveAsset'
90+runServeAsset :: FilePath -> [Text] -> Snap ()
91+runServeAsset rootPath = Snap.serveAsset "" rootPath . T.unpack . T.intercalate "/"
92+
90getConfigRoute :: IO (Maybe URI)
93getConfigRoute :: IO (Maybe URI)
91getConfigRoute = get "config/common/route" >>= \case
94getConfigRoute = get "config/common/route" >>= \case
92 Just r -> case URI.mkURI $ T.strip r of
95 Just r -> case URI.mkURI $ T.strip r of
@@ -99,7 +102,7 @@ getConfigRoute = get "config/common/route" >>= \case
99defAppUri :: URI
102defAppUri :: URI
100defAppUri = fromMaybe (error "defAppUri") $ URI.mkURI "http://127.0.0.1:8000"
103defAppUri = fromMaybe (error "defAppUri") $ URI.mkURI "http://127.0.0.1:8000"
101
104
102−runWidget :: RunConfig -> Frontend (R route) -> ValidEncoder (Either Text) (R (Sum backendRoute (ObeliskRoute route))) PageName -> IO ()
105+runWidget :: RunConfig -> Frontend (R route) -> Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName -> IO ()
103runWidget conf frontend validFullEncoder = do
106runWidget conf frontend validFullEncoder = do
104 uri <- fromMaybe defAppUri <$> getConfigRoute
107 uri <- fromMaybe defAppUri <$> getConfigRoute
105 let port = fromIntegral $ fromMaybe 80 $ uri ^? uriAuthority . _Right . authPort . _Just
108 let port = fromIntegral $ fromMaybe 80 $ uri ^? uriAuthority . _Right . authPort . _Just
@@ -119,7 +122,7 @@ runWidget conf frontend validFullEncoder = do
119obeliskApp
122obeliskApp
120 :: forall route backendRoute. ConnectionOptions
123 :: forall route backendRoute. ConnectionOptions
121 -> Frontend (R route)
124 -> Frontend (R route)
122− -> ValidEncoder (Either Text) (R (Sum backendRoute (ObeliskRoute route))) PageName
125+ -> Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
123 -> URI
126 -> URI
124 -> Application
127 -> Application
125 -> IO Application
128 -> IO Application
@@ -129,15 +132,14 @@ obeliskApp opts frontend validFullEncoder uri backend = do
129 syncPoint
132 syncPoint
130 jsaddlePath <- URI.mkPathPiece "jsaddle"
133 jsaddlePath <- URI.mkPathPiece "jsaddle"
131 let jsaddleUri = BSLC.fromStrict $ URI.renderBs $ uri & uriPath %~ (<>[jsaddlePath])
134 let jsaddleUri = BSLC.fromStrict $ URI.renderBs $ uri & uriPath %~ (<>[jsaddlePath])
132− Right (jsaddleWarpRouteValidEncoder :: ValidEncoder (Either Text) (R JSaddleWarpRoute) PageName) <- return $ checkEncoder jsaddleWarpRouteEncoder
135+ Right (jsaddleWarpRouteValidEncoder :: Encoder Identity (Either Text) (R JSaddleWarpRoute) PageName) <- return $ checkEncoder jsaddleWarpRouteEncoder
133 jsaddle <- jsaddleWithAppOr opts entryPoint $ \_ sendResponse -> sendResponse $ W.responseLBS H.status500 [("Content-Type", "text/plain")] "obeliskApp: jsaddle got a bad URL"
136 jsaddle <- jsaddleWithAppOr opts entryPoint $ \_ sendResponse -> sendResponse $ W.responseLBS H.status500 [("Content-Type", "text/plain")] "obeliskApp: jsaddle got a bad URL"
134− return $ \req sendResponse -> case _validEncoder_decode validFullEncoder (W.pathInfo req, mempty) of --TODO: Query strings
137+ return $ \req sendResponse -> case tryDecode validFullEncoder (W.pathInfo req, mempty) of --TODO: Query strings
135− Left e -> sendResponse $ W.responseLBS H.status404 [("Content-Type", "text/plain")] $ LBS.fromStrict $ encodeUtf8 e
136− Right r -> case r of
138+ Identity r -> case r of
137 InR (ObeliskRoute_Resource ResourceRoute_JSaddleWarp) :=> Identity jsaddleRoute -> case jsaddleRoute of
139 InR (ObeliskRoute_Resource ResourceRoute_JSaddleWarp) :=> Identity jsaddleRoute -> case jsaddleRoute of
138 JSaddleWarpRoute_JavaScript :/ () -> sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] $ jsaddleJs' (Just jsaddleUri) False
140 JSaddleWarpRoute_JavaScript :/ () -> sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] $ jsaddleJs' (Just jsaddleUri) False
139 _ -> flip jsaddle sendResponse $ req
141 _ -> flip jsaddle sendResponse $ req
140− { W.pathInfo = fst $ _validEncoder_encode jsaddleWarpRouteValidEncoder jsaddleRoute
142+ { W.pathInfo = fst $ encode jsaddleWarpRouteValidEncoder jsaddleRoute
141 }
143 }
142 InR (ObeliskRoute_App appRouteComponent) :=> Identity appRouteRest -> do
144 InR (ObeliskRoute_App appRouteComponent) :=> Identity appRouteRest -> do
143 html <- renderJsaddleFrontend (appRouteComponent :/ appRouteRest) frontend
145 html <- renderJsaddleFrontend (appRouteComponent :/ appRouteRest) frontend
lib/selftest/src/Obelisk/SelfTest.hs+24−20
@@ -1,3 +1,4 @@
1+{-# LANGUAGE EmptyCase #-}
1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE PatternGuards #-}
4{-# LANGUAGE PatternGuards #-}
@@ -14,6 +15,7 @@ import Data.Semigroup (Semigroup, (<>))
14import qualified Data.Set as Set
15import qualified Data.Set as Set
15import Data.String
16import Data.String
16import Data.Text (Text)
17import Data.Text (Text)
18+import Data.Void
17import qualified Data.Text as T
19import qualified Data.Text as T
18import qualified Data.Text.IO as T
20import qualified Data.Text.IO as T
19import qualified Network.HTTP.Client as HTTP
21import qualified Network.HTTP.Client as HTTP
@@ -26,19 +28,18 @@ import System.Exit (ExitCode (..))
26import System.Info
28import System.Info
27import System.IO (Handle, hClose)
29import System.IO (Handle, hClose)
28import System.IO.Temp
30import System.IO.Temp
29−import System.Process (readProcessWithExitCode)
31+import System.Process (readProcessWithExitCode, CreateProcess(cwd), readCreateProcessWithExitCode, proc)
30−import System.Timeout
31import Test.Hspec
32import Test.Hspec
32import Test.HUnit.Base
33import Test.HUnit.Base
33
34
34−import Obelisk.CliApp hiding (runCli)
35+import Obelisk.CliApp hiding (runCli, readCreateProcessWithExitCode)
35import qualified Obelisk.CliApp as CliApp
36import qualified Obelisk.CliApp as CliApp
36import Obelisk.Run (getConfigRoute)
37import Obelisk.Run (getConfigRoute)
37
38
38data ObRunState
39data ObRunState
39 = ObRunState_Init
40 = ObRunState_Init
40 | ObRunState_Startup
41 | ObRunState_Startup
41− | ObRunState_BackendStarted Text -- Port of backend
42+ | ObRunState_BackendStarted
42 deriving (Eq, Show)
43 deriving (Eq, Show)
43
44
44doubleQuotes :: (IsString a, Semigroup a) => a -> a
45doubleQuotes :: (IsString a, Semigroup a) => a -> a
@@ -53,9 +54,10 @@ commit msg = void $ run "git"
53 , doubleQuotes msg
54 , doubleQuotes msg
54 ]
55 ]
55
56
57+-- TODO replace Void and stop using loosely typed synchronous exceptions
56−runCli :: MonadIO m => CliT IO a -> m a
58+runCli :: MonadIO m => CliT Void IO a -> m a
57runCli f = liftIO $ do
59runCli f = liftIO $ do
58− c <- newCliConfig Notice False False
60+ c <- newCliConfig Notice False False (\case {})
59 CliApp.runCli c f
61 CliApp.runCli c f
60
62
61tshow :: Show a => a -> Text
63tshow :: Show a => a -> Text
@@ -119,6 +121,12 @@ main = do
119 it "works with default impl" $ inTmp $ \_ -> run "ob" ["init"]
121 it "works with default impl" $ inTmp $ \_ -> run "ob" ["init"]
120 it "works with master branch impl" $ inTmp $ \_ -> run "ob" ["init", "--branch", "master"]
122 it "works with master branch impl" $ inTmp $ \_ -> run "ob" ["init", "--branch", "master"]
121 it "works with symlink" $ inTmp $ \_ -> run "ob" ["init", "--symlink", obeliskImpl]
123 it "works with symlink" $ inTmp $ \_ -> run "ob" ["init", "--symlink", obeliskImpl]
124+ it "doesn't silently overwrite existing files" $ withSystemTempDirectory "ob-init" $ \dir -> do
125+ let p force = (proc "ob" $ "--no-handoff" : "init" : if force then ["--force"] else []) { cwd = Just dir }
126+ (ExitSuccess, _, _) <- readCreateProcessWithExitCode (p False) ""
127+ (ExitFailure _, _, _) <- readCreateProcessWithExitCode (p False) ""
128+ (ExitSuccess, _, _) <- readCreateProcessWithExitCode (p True) ""
129+ pure ()
122
130
123 it "doesn't create anything when given an invalid impl" $ inTmp $ \tmp -> do
131 it "doesn't create anything when given an invalid impl" $ inTmp $ \tmp -> do
124 void $ errExit False $ run "ob" ["init", "--symlink", "/dev/null"]
132 void $ errExit False $ run "ob" ["init", "--symlink", "/dev/null"]
@@ -214,12 +222,12 @@ testObRunInDir mdir httpManager = handle_sh (\case ExitSuccess -> pure (); e ->
214 writefile "config/common/route" . uri =<< liftIO getFreePort -- Use a free port initially
222 writefile "config/common/route" . uri =<< liftIO getFreePort -- Use a free port initially
215 maybe id chdir mdir $ runHandle "ob" ["run"] $ \stdout -> do
223 maybe id chdir mdir $ runHandle "ob" ["run"] $ \stdout -> do
216 freePort <- liftIO getFreePort
224 freePort <- liftIO getFreePort
217− (_, firstUri) <- handleObRunStdout httpManager stdout
225+ firstUri <- handleObRunStdout httpManager stdout
218 let newUri = uri freePort
226 let newUri = uri freePort
219 when (firstUri == newUri) $ errorExit $
227 when (firstUri == newUri) $ errorExit $
220 "Startup URI (" <> firstUri <> ") is the same as test URI (" <> newUri <> ")"
228 "Startup URI (" <> firstUri <> ") is the same as test URI (" <> newUri <> ")"
221 maybe id (\_ -> chdir "..") mdir $ alterRouteTo newUri stdout
229 maybe id (\_ -> chdir "..") mdir $ alterRouteTo newUri stdout
222− (_, runningUri) <- handleObRunStdout httpManager stdout
230+ runningUri <- handleObRunStdout httpManager stdout
223 if runningUri /= newUri
231 if runningUri /= newUri
224 then errorExit $ "Reloading failed: expected " <> newUri <> " but got " <> runningUri
232 then errorExit $ "Reloading failed: expected " <> newUri <> " but got " <> runningUri
225 else exit 0
233 else exit 0
@@ -266,32 +274,28 @@ alterRouteTo uri stdout = do
266 "Reloading failed: " <> T.pack (show t)
274 "Reloading failed: " <> T.pack (show t)
267
275
268-- | Handle stdout of `ob run`: check that the frontend and backend servers are started correctly
276-- | Handle stdout of `ob run`: check that the frontend and backend servers are started correctly
269−handleObRunStdout :: HTTP.Manager -> Handle -> Sh (Text, Text)
277+handleObRunStdout :: HTTP.Manager -> Handle -> Sh Text
270handleObRunStdout httpManager stdout = flip fix ObRunState_Init $ \loop state -> do
278handleObRunStdout httpManager stdout = flip fix ObRunState_Init $ \loop state -> do
271 liftIO (T.hGetLine stdout) >>= \t -> case state of
279 liftIO (T.hGetLine stdout) >>= \t -> case state of
272 ObRunState_Init
280 ObRunState_Init
273− | "Running test..." `T.isPrefixOf` t -> loop ObRunState_Startup
281+ | "Running test..." `T.isPrefixOf` t -> loop ObRunState_BackendStarted
274 ObRunState_Startup
282 ObRunState_Startup
275 | t == "backend stopped; make a change to your code to reload" -> loop ObRunState_Startup
283 | t == "backend stopped; make a change to your code to reload" -> loop ObRunState_Startup
276− | Just port <- "Backend running on port " `T.stripPrefix` t -> loop $ ObRunState_BackendStarted port
284+ -- | Just port <- "Backend running on port " `T.stripPrefix` t -> loop $ ObRunState_BackendStarted port
277 | not (T.null t) -> errorExit $ "Startup: " <> t -- If theres any other output here, startup failed
285 | not (T.null t) -> errorExit $ "Startup: " <> t -- If theres any other output here, startup failed
278− ObRunState_BackendStarted port
286+ ObRunState_BackendStarted
279 | Just uri <- "Frontend running on " `T.stripPrefix` t -> do
287 | Just uri <- "Frontend running on " `T.stripPrefix` t -> do
280− obRunCheck httpManager stdout port uri
288+ obRunCheck httpManager stdout uri
281− pure (port, uri)
289+ pure uri
282 | not (T.null t) -> errorExit $ "Started: " <> t -- If theres any other output here, startup failed
290 | not (T.null t) -> errorExit $ "Started: " <> t -- If theres any other output here, startup failed
283 _ -> loop state
291 _ -> loop state
284
292
285-- | Make requests to frontend/backend servers to check they are working properly
293-- | Make requests to frontend/backend servers to check they are working properly
286−obRunCheck :: HTTP.Manager -> Handle -> Text -> Text -> Sh ()
294+obRunCheck :: HTTP.Manager -> Handle -> Text -> Sh ()
287−obRunCheck httpManager stdout backendPort frontendUri = do
295+obRunCheck httpManager _stdout frontendUri = do
288 let req uri = liftIO $ HTTP.parseRequest (T.unpack uri) >>= flip HTTP.httpLbs httpManager
296 let req uri = liftIO $ HTTP.parseRequest (T.unpack uri) >>= flip HTTP.httpLbs httpManager
289 req frontendUri >>= \r -> when (HTTP.responseStatus r /= HTTP.ok200) $ errorExit $
297 req frontendUri >>= \r -> when (HTTP.responseStatus r /= HTTP.ok200) $ errorExit $
290 "Request to frontend server failed: " <> T.pack (show r)
298 "Request to frontend server failed: " <> T.pack (show r)
291− void $ req $ "http://localhost:" <> backendPort
292− liftIO (timeout 1000000 $ T.hGetLine stdout) >>= \case
293− Just t | "127.0.0.1" `T.isPrefixOf` t -> pure () -- Backend request logged
294− x -> errorExit $ "Couldn't get backend request log: " <> T.pack (show x)
295
299
296getFreePort :: IO Socket.PortNumber
300getFreePort :: IO Socket.PortNumber
297getFreePort = Socket.withSocketsDo $ do
301getFreePort = Socket.withSocketsDo $ do
migration/001efbbe866b66eebc6ee1ea0a906f2d-b0c1d039514a686529561f186332c91e/obelisk-handoffdeleted

No changes to the content.

migration/001efbbe866b66eebc6ee1ea0a906f2d-b0c1d039514a686529561f186332c91e/obelisk-upgradedeleted

No changes to the content.

migration/00a480749e96ef7e21fbe8f4943152b1-abdb013baf130e1bd7e7fd04e535c44c/obelisk-handoffdeleted

No changes to the content.

migration/00a480749e96ef7e21fbe8f4943152b1-abdb013baf130e1bd7e7fd04e535c44c/obelisk-upgradedeleted

No changes to the content.

migration/00bee4377690b5040bd5b3702dce2a88-98629a336dcd5fa3524a09f8ea6ad636/obelisk-handoffdeleted

No changes to the content.

migration/00bee4377690b5040bd5b3702dce2a88-98629a336dcd5fa3524a09f8ea6ad636/obelisk-upgradedeleted

No changes to the content.

migration/00c627540326314c7154cc209e8a71e9-67bfa5b784a35e66875fb74ad84a38c0/obelisk-handoffdeleted

No changes to the content.

migration/00c627540326314c7154cc209e8a71e9-67bfa5b784a35e66875fb74ad84a38c0/obelisk-upgradedeleted

No changes to the content.

migration/01c0ee6c93a3f9f69e097c41169927ac-2913c440c7ad7731aae96e60a86bf7a8/obelisk-handoffdeleted

No changes to the content.

migration/01c0ee6c93a3f9f69e097c41169927ac-2913c440c7ad7731aae96e60a86bf7a8/obelisk-upgradedeleted

No changes to the content.

migration/01ddc6354698efa1ae5822da3be562dc-a536d01b9c489f5e00e30abf27427da0/obelisk-handoffdeleted

No changes to the content.

migration/01ddc6354698efa1ae5822da3be562dc-a536d01b9c489f5e00e30abf27427da0/obelisk-upgradedeleted

No changes to the content.

migration/01fb7610e4184ad1b491267fcb0c7f43-c0c1452986b8d4c181bb0c6c83c56a2f/obelisk-handoffdeleted

No changes to the content.

migration/01fb7610e4184ad1b491267fcb0c7f43-c0c1452986b8d4c181bb0c6c83c56a2f/obelisk-upgradedeleted

No changes to the content.

migration/0298e61f6a03e553df08dcef03e27bd1-06d184d1fb27966294a8fee1c7df7cb6/obelisk-handoffdeleted

No changes to the content.

migration/0298e61f6a03e553df08dcef03e27bd1-06d184d1fb27966294a8fee1c7df7cb6/obelisk-upgradedeleted

No changes to the content.

migration/02d0f8b5c7472e6f8a99ec2cfcb59807-5b6770965bbef440db49b0703cd243a3/obelisk-handoffdeleted

No changes to the content.

migration/02d0f8b5c7472e6f8a99ec2cfcb59807-5b6770965bbef440db49b0703cd243a3/obelisk-upgradedeleted

No changes to the content.

migration/02f532fd8bb64a167a5349c2ba8e3331-1088e2464720e12067dd74683546b132/obelisk-handoffdeleted

No changes to the content.

migration/02f532fd8bb64a167a5349c2ba8e3331-1088e2464720e12067dd74683546b132/obelisk-upgradedeleted

No changes to the content.

migration/02fb88dab35884e09deef1721c1cfc13-ae824b83b569b3b23b8e198190371ca0/obelisk-handoffdeleted

No changes to the content.

migration/02fb88dab35884e09deef1721c1cfc13-ae824b83b569b3b23b8e198190371ca0/obelisk-upgradedeleted

No changes to the content.

migration/030ed18dacf3571cbb9e3e7d9d99627b-d37d70fe5d7d11e68b59ce12022281c9/obelisk-handoffdeleted

No changes to the content.

migration/030ed18dacf3571cbb9e3e7d9d99627b-d37d70fe5d7d11e68b59ce12022281c9/obelisk-upgradedeleted

No changes to the content.

migration/03b845f308e73a1567eb843dfca182d2-e9ec84bfe7099b713c0fda32308f5815/obelisk-handoffdeleted

No changes to the content.

migration/03b845f308e73a1567eb843dfca182d2-e9ec84bfe7099b713c0fda32308f5815/obelisk-upgradedeleted

No changes to the content.

migration/03b9747848d7f3e995b118f776ef96c7-3da702ef7f531ad6db874ac3f85e9c06/obelisk-handoffdeleted−1
@@ -1 +0,0 @@
1−True
migration/03b9747848d7f3e995b118f776ef96c7-3da702ef7f531ad6db874ac3f85e9c06/obelisk-upgradedeleted

No changes to the content.

migration/04a5ae41e8fcef5f719cb84d01430382-f391c6a74ee14217ce4565c278fceed6/obelisk-handoffdeleted

No changes to the content.

migration/04a5ae41e8fcef5f719cb84d01430382-f391c6a74ee14217ce4565c278fceed6/obelisk-upgradedeleted

No changes to the content.

migration/04dd95ecb889ac1b0a616ddfeec781c4-f790ac9400501511b4ee045e22bc5fe1/obelisk-handoffdeleted

No changes to the content.

migration/04dd95ecb889ac1b0a616ddfeec781c4-f790ac9400501511b4ee045e22bc5fe1/obelisk-upgradedeleted

No changes to the content.

migration/0535e06dd0d61f3e6970b491a17cf1f8-28a6ada033c86e5ae914cc31cecb6239/obelisk-handoffdeleted

No changes to the content.

migration/0535e06dd0d61f3e6970b491a17cf1f8-28a6ada033c86e5ae914cc31cecb6239/obelisk-upgradedeleted

No changes to the content.

migration/053c0b2f84a7a2eadeb5c3f9575ffa0d-2600ab2ec96d3177d2a6b3462e09862d/obelisk-handoffdeleted

No changes to the content.

migration/053c0b2f84a7a2eadeb5c3f9575ffa0d-2600ab2ec96d3177d2a6b3462e09862d/obelisk-upgradedeleted

No changes to the content.

migration/063665b78b6705e7de952f73fa9797bf-172e629829334d2b33cda8d3b5e0b148/obelisk-handoffdeleted

No changes to the content.

migration/063665b78b6705e7de952f73fa9797bf-172e629829334d2b33cda8d3b5e0b148/obelisk-upgradedeleted

No changes to the content.

migration/06d184d1fb27966294a8fee1c7df7cb6-b1692927da5ed59af72fac5be8094fa2/obelisk-handoffdeleted

No changes to the content.

migration/06d184d1fb27966294a8fee1c7df7cb6-b1692927da5ed59af72fac5be8094fa2/obelisk-upgradedeleted

No changes to the content.

migration/071f5b3326b8886de3eddc927b80eb10-28e25b7a8812869a709579cd34e7bcb4/obelisk-handoffdeleted

No changes to the content.

migration/071f5b3326b8886de3eddc927b80eb10-28e25b7a8812869a709579cd34e7bcb4/obelisk-upgradedeleted

No changes to the content.

migration/07900e6c41ab9a78917f66fcf7e0d9b8-554c459ff2cced3e1b11241ed403890d/obelisk-handoffdeleted

No changes to the content.

migration/07900e6c41ab9a78917f66fcf7e0d9b8-554c459ff2cced3e1b11241ed403890d/obelisk-upgradedeleted

No changes to the content.

migration/07900e6c41ab9a78917f66fcf7e0d9b8-8c84f81d9b09a5e0f028f3c6341ffb36/obelisk-handoffdeleted

No changes to the content.

migration/07900e6c41ab9a78917f66fcf7e0d9b8-8c84f81d9b09a5e0f028f3c6341ffb36/obelisk-upgradedeleted−15
@@ -1,15 +0,0 @@
1−# ob deploy: HTTPs support
2−
3−Obelisk deployments now support HTTPs. You must upgrade the deployment repo
4−associated with the obelisk project as follows:
5−
6−```
7−cd /your/project's/deployment/repo
8−echo True > enable_https
9−echo admin@yoursite.com > admin_email
10−echo https://yoursite.com/ > config/common/route
11−git add ... && git commit ...
12−ob deploy push
13−```
14−
15−If you do not wish to enable HTTPs, write `False` to `enable_https` instead.
migration/0804ef73b54a4dc7fbde18147f33b4f9-00bee4377690b5040bd5b3702dce2a88/obelisk-handoffdeleted

No changes to the content.

migration/0804ef73b54a4dc7fbde18147f33b4f9-00bee4377690b5040bd5b3702dce2a88/obelisk-upgradedeleted

No changes to the content.

migration/08dbdf23867a8efa680283ad251d3429-f2a9ba4a93c3a04acbee2211bca9e27a/obelisk-handoffdeleted

No changes to the content.

migration/08dbdf23867a8efa680283ad251d3429-f2a9ba4a93c3a04acbee2211bca9e27a/obelisk-upgradedeleted

No changes to the content.

migration/0abd9d31c77be8c9a9e83bf1f73276f6-fc42c5fc24031fc6a8836a10cfb45143/obelisk-handoffdeleted

No changes to the content.

migration/0abd9d31c77be8c9a9e83bf1f73276f6-fc42c5fc24031fc6a8836a10cfb45143/obelisk-upgradedeleted

No changes to the content.

migration/0b2cab546a1526414096980fca85d0d3-99aebbfcac51725464bc59571b7d71b8/obelisk-handoffdeleted

No changes to the content.

migration/0b2cab546a1526414096980fca85d0d3-99aebbfcac51725464bc59571b7d71b8/obelisk-upgradedeleted

No changes to the content.

migration/0c6127453069ad707e34ddfedb67e2f9-7c98ef4a123c1a580d368d14e54961b7/obelisk-handoffdeleted

No changes to the content.

migration/0c6127453069ad707e34ddfedb67e2f9-7c98ef4a123c1a580d368d14e54961b7/obelisk-upgradedeleted

No changes to the content.

migration/0c9826e42d5d4fa9988c7db53722826e-c56de2f95c90def16704f62639246bbc/obelisk-handoffdeleted

No changes to the content.

migration/0c9826e42d5d4fa9988c7db53722826e-c56de2f95c90def16704f62639246bbc/obelisk-upgradedeleted

No changes to the content.

migration/0c991bc3c06801f72df48486ee78e350-58479e482d3828ce44f2c8371ed9135a/obelisk-handoffdeleted

No changes to the content.

migration/0c991bc3c06801f72df48486ee78e350-58479e482d3828ce44f2c8371ed9135a/obelisk-upgradedeleted

No changes to the content.

migration/0d3b4cfe9b4c72b830780a4c44942176-214979923f2d7e6278856493c1978fdc/obelisk-handoffdeleted

No changes to the content.

migration/0d3b4cfe9b4c72b830780a4c44942176-214979923f2d7e6278856493c1978fdc/obelisk-upgradedeleted

No changes to the content.

migration/0da1150f0e9fe57d1fafbb6cabe24fa1-a632452cf9c5956becd1a40512472b43/obelisk-handoffdeleted

No changes to the content.

migration/0da1150f0e9fe57d1fafbb6cabe24fa1-a632452cf9c5956becd1a40512472b43/obelisk-upgradedeleted

No changes to the content.

migration/0e4d0f0f0af0925474b0d772be2b145f-b82e9c3fe22feff4bb3a6be49943fd6c/obelisk-handoffdeleted

No changes to the content.

migration/0e4d0f0f0af0925474b0d772be2b145f-b82e9c3fe22feff4bb3a6be49943fd6c/obelisk-upgradedeleted

No changes to the content.

migration/0ec6f67d94d3913cf3243d813238ec39-0804ef73b54a4dc7fbde18147f33b4f9/obelisk-handoffdeleted

No changes to the content.

migration/0ec6f67d94d3913cf3243d813238ec39-0804ef73b54a4dc7fbde18147f33b4f9/obelisk-upgradedeleted

No changes to the content.

migration/0f78d9d6875933336bbf0c1c8888b73f-d1b3f5f5375558bf91d55c88547784b9/obelisk-handoffdeleted

No changes to the content.

migration/0f78d9d6875933336bbf0c1c8888b73f-d1b3f5f5375558bf91d55c88547784b9/obelisk-upgradedeleted

No changes to the content.

migration/0fb3a912a8aaf5ef6f734a25f0bce7e2-cccf4140dc336263489bcf67c8c58f86/obelisk-handoffdeleted

No changes to the content.

migration/0fb3a912a8aaf5ef6f734a25f0bce7e2-cccf4140dc336263489bcf67c8c58f86/obelisk-upgradedeleted

No changes to the content.

migration/0fba399e046852d4ecf8e1dad831a2f9-6620e6dc5622593e5f671127af9ade05/obelisk-handoffdeleted

No changes to the content.

migration/0fba399e046852d4ecf8e1dad831a2f9-6620e6dc5622593e5f671127af9ade05/obelisk-upgradedeleted

No changes to the content.

migration/0fe60df438daa8a2cca5f2b569e361a5-2e3e85cc59aa31f24f18c027542c0510/obelisk-handoffdeleted

No changes to the content.

migration/0fe60df438daa8a2cca5f2b569e361a5-2e3e85cc59aa31f24f18c027542c0510/obelisk-upgradedeleted

No changes to the content.

migration/1088e2464720e12067dd74683546b132-b2282679f6f63cec89d921c3dd512d1e/obelisk-handoffdeleted

No changes to the content.

migration/1088e2464720e12067dd74683546b132-b2282679f6f63cec89d921c3dd512d1e/obelisk-upgradedeleted

No changes to the content.

migration/11a5917ea91e021f1fa541a2fe158c80-d8adb57b2f2266e128ebb6f6d7e65920/obelisk-handoffdeleted

No changes to the content.

migration/11a5917ea91e021f1fa541a2fe158c80-d8adb57b2f2266e128ebb6f6d7e65920/obelisk-upgradedeleted

No changes to the content.

migration/11f38b38c2d38004bb8a9dbfe2ef3e4a-29d9b8b8ae9698888f3ea90b10ff5e92/obelisk-handoffdeleted

No changes to the content.

migration/11f38b38c2d38004bb8a9dbfe2ef3e4a-29d9b8b8ae9698888f3ea90b10ff5e92/obelisk-upgradedeleted

No changes to the content.

migration/1269155fffe21926f3704d5077e04332-1a97559d049bf85cebb762b7e7b70db1/obelisk-handoffdeleted

No changes to the content.

migration/1269155fffe21926f3704d5077e04332-1a97559d049bf85cebb762b7e7b70db1/obelisk-upgradedeleted

No changes to the content.

migration/12c8a24c34c2d8441f3181d179c2baab-bd6728737dffcc3505d30ada344e2f4b/obelisk-handoffdeleted

No changes to the content.

migration/12c8a24c34c2d8441f3181d179c2baab-bd6728737dffcc3505d30ada344e2f4b/obelisk-upgradedeleted

No changes to the content.

migration/131e1d5cc8163ce9a174a8782be23fe7-e323ce1352e512109f1609276fb509b4/obelisk-handoffdeleted

No changes to the content.

migration/131e1d5cc8163ce9a174a8782be23fe7-e323ce1352e512109f1609276fb509b4/obelisk-upgradedeleted

No changes to the content.

migration/13f6767460e94079cdfa6d5e1df79ecb-c106f9398d5c203a1a296b85c7411bf9/obelisk-handoffdeleted

No changes to the content.

migration/13f6767460e94079cdfa6d5e1df79ecb-c106f9398d5c203a1a296b85c7411bf9/obelisk-upgradedeleted

No changes to the content.

migration/145650b66e83adf5bdb8261593076379-e2be519915794ef130249f2be7640382/obelisk-handoffdeleted

No changes to the content.

migration/145650b66e83adf5bdb8261593076379-e2be519915794ef130249f2be7640382/obelisk-upgradedeleted

No changes to the content.

migration/14840816db4b5cfd5580ae099022604a-aaf979dd0121603a8b48127e1ea47c7d/obelisk-handoffdeleted

No changes to the content.

migration/14840816db4b5cfd5580ae099022604a-aaf979dd0121603a8b48127e1ea47c7d/obelisk-upgradedeleted

No changes to the content.

migration/15f3f3f517b38664131b85da0594522f-9715967793be68dfd8c9a7d631f8d589/obelisk-handoffdeleted

No changes to the content.

migration/15f3f3f517b38664131b85da0594522f-9715967793be68dfd8c9a7d631f8d589/obelisk-upgradedeleted

No changes to the content.

migration/1629d0d70e360df6b2280fac36d21e8a-a825fbb74e8d050a5ce10ec4c62b83f5/obelisk-handoffdeleted

No changes to the content.

migration/1629d0d70e360df6b2280fac36d21e8a-a825fbb74e8d050a5ce10ec4c62b83f5/obelisk-upgradedeleted

No changes to the content.

migration/166c5f8e4a0f1dfd227b075a96237386-a5904e3a899e346fe2fe26314f69c075/obelisk-handoffdeleted

No changes to the content.

migration/166c5f8e4a0f1dfd227b075a96237386-a5904e3a899e346fe2fe26314f69c075/obelisk-upgradedeleted

No changes to the content.

migration/16b24e1a12b93ea83d7656a09d5e3338-8a64ec5f2ee43d69607499f085560857/obelisk-handoffdeleted

No changes to the content.

migration/16b24e1a12b93ea83d7656a09d5e3338-8a64ec5f2ee43d69607499f085560857/obelisk-upgradedeleted

No changes to the content.

migration/17089ea0edc0cf4b6f616936f8a06174-a34d758508a0a5d08bc10e76482c4d11/obelisk-handoffdeleted

No changes to the content.

migration/17089ea0edc0cf4b6f616936f8a06174-a34d758508a0a5d08bc10e76482c4d11/obelisk-upgradedeleted

No changes to the content.

migration/172e629829334d2b33cda8d3b5e0b148-a808a04519bbf2874e41028ded9c180c/obelisk-handoffdeleted

No changes to the content.

migration/172e629829334d2b33cda8d3b5e0b148-a808a04519bbf2874e41028ded9c180c/obelisk-upgradedeleted

No changes to the content.

migration/174fa1072ee767be39b7b01aecea5714-6566f0555737425bae9106d1a4a0a2dd/obelisk-handoffdeleted

No changes to the content.

migration/174fa1072ee767be39b7b01aecea5714-6566f0555737425bae9106d1a4a0a2dd/obelisk-upgradedeleted

No changes to the content.

migration/1859cc62bb2f89f528bb2aa170c36e84-be5a7b436cef0ae35cb074700d5f0d00/obelisk-handoffdeleted

No changes to the content.

migration/1859cc62bb2f89f528bb2aa170c36e84-be5a7b436cef0ae35cb074700d5f0d00/obelisk-upgradedeleted

No changes to the content.

migration/190460dabd2b9773c3fe81902d0c8a62-e7385c9eae2b807a483b73be4295b207/obelisk-handoffdeleted

No changes to the content.

migration/190460dabd2b9773c3fe81902d0c8a62-e7385c9eae2b807a483b73be4295b207/obelisk-upgradedeleted

No changes to the content.

migration/1a08b154ae5f898992128e229090c4d8-4e95e2f8ef50420371b71022c644e25d/obelisk-handoffdeleted

No changes to the content.

migration/1a08b154ae5f898992128e229090c4d8-4e95e2f8ef50420371b71022c644e25d/obelisk-upgradedeleted

No changes to the content.

migration/1a97559d049bf85cebb762b7e7b70db1-5cb98d0250bf5224b94e544a5140a54c/obelisk-handoffdeleted

No changes to the content.

migration/1a97559d049bf85cebb762b7e7b70db1-5cb98d0250bf5224b94e544a5140a54c/obelisk-upgradedeleted

No changes to the content.

migration/1ada2d50d7847a12dbe29df4097b5bdc-11a5917ea91e021f1fa541a2fe158c80/obelisk-handoffdeleted

No changes to the content.

migration/1ada2d50d7847a12dbe29df4097b5bdc-11a5917ea91e021f1fa541a2fe158c80/obelisk-upgradedeleted

No changes to the content.

migration/1b1686b03b41bd840ff46776cec7b33a-31a676fd9e1f69bd07380480ec879da0/obelisk-handoffdeleted

No changes to the content.

migration/1b1686b03b41bd840ff46776cec7b33a-31a676fd9e1f69bd07380480ec879da0/obelisk-upgradedeleted

No changes to the content.

migration/1c1c3f2b4426d5d26377eef08dbccb3c-29abdd56a2dbbf3a2a0715210d441d06/obelisk-handoffdeleted

No changes to the content.

migration/1c1c3f2b4426d5d26377eef08dbccb3c-29abdd56a2dbbf3a2a0715210d441d06/obelisk-upgradedeleted

No changes to the content.

migration/1c2c93819c07f33ad899e39ec6769c3c-909b6b2066cbdc991e6c16455f13637c/obelisk-handoffdeleted

No changes to the content.

migration/1c2c93819c07f33ad899e39ec6769c3c-909b6b2066cbdc991e6c16455f13637c/obelisk-upgradedeleted

No changes to the content.

migration/1c4fc23cbf5653613c6060668e065f37-5b252c24612c2ba868580add9d9c19e3/obelisk-handoffdeleted

No changes to the content.

migration/1c4fc23cbf5653613c6060668e065f37-5b252c24612c2ba868580add9d9c19e3/obelisk-upgradedeleted

No changes to the content.

migration/1c9bdb5c544f2e5432f549653a61dabe-20926beb4c30e2efa819d23e5694645e/obelisk-handoffdeleted

No changes to the content.

migration/1c9bdb5c544f2e5432f549653a61dabe-20926beb4c30e2efa819d23e5694645e/obelisk-upgradedeleted

No changes to the content.

migration/1cc8d98f6f61441570fdde2d697e6e3c-69fafe6ab155ff215b6d9d330f700ba1/obelisk-handoffdeleted

No changes to the content.

migration/1cc8d98f6f61441570fdde2d697e6e3c-69fafe6ab155ff215b6d9d330f700ba1/obelisk-upgradedeleted

No changes to the content.

migration/1d084e187591225f0e8fa6fa824f8863-02f532fd8bb64a167a5349c2ba8e3331/obelisk-handoffdeleted

No changes to the content.

migration/1d084e187591225f0e8fa6fa824f8863-02f532fd8bb64a167a5349c2ba8e3331/obelisk-upgradedeleted

No changes to the content.

migration/1df871cede0758cabe43971d9091279d-0b2cab546a1526414096980fca85d0d3/obelisk-handoffdeleted

No changes to the content.

migration/1df871cede0758cabe43971d9091279d-0b2cab546a1526414096980fca85d0d3/obelisk-upgradedeleted

No changes to the content.

migration/1e36dc8817e2784df36d643b7499092d-ba0ccd5ef6110f6c5133d9d6ea6277f0/obelisk-handoffdeleted

No changes to the content.

migration/1e36dc8817e2784df36d643b7499092d-ba0ccd5ef6110f6c5133d9d6ea6277f0/obelisk-upgradedeleted

No changes to the content.

migration/1e376437f069662df26178e4a7873190-ca5ee8f372ecb07b09283081f0298733/obelisk-handoffdeleted

No changes to the content.

migration/1e376437f069662df26178e4a7873190-ca5ee8f372ecb07b09283081f0298733/obelisk-upgradedeleted

No changes to the content.

migration/1f09fcf196bfe90ed52f113c38ffbfdb-d16784e9a2d93db7f953941e3003fa8c/obelisk-handoffdeleted

No changes to the content.

migration/1f09fcf196bfe90ed52f113c38ffbfdb-d16784e9a2d93db7f953941e3003fa8c/obelisk-upgradedeleted

No changes to the content.

migration/1f5b6e2a19c87d3820b953f942ab4a27-8b098f49e1fd1aa10ca22b9dc312644e/obelisk-handoffdeleted

No changes to the content.

migration/1f5b6e2a19c87d3820b953f942ab4a27-8b098f49e1fd1aa10ca22b9dc312644e/obelisk-upgradedeleted

No changes to the content.

migration/1f7fb90cb1e558f5285b2d19a25a1078-5a98cdb6c195e88beac878a3f311c5b5/obelisk-handoffdeleted

No changes to the content.

migration/1f7fb90cb1e558f5285b2d19a25a1078-5a98cdb6c195e88beac878a3f311c5b5/obelisk-upgradedeleted

No changes to the content.

migration/1fe00204f9d2cd653ff2894eae3d9356-4b3f45ce8aeb17ef18d1d9502b74bb7e/obelisk-handoffdeleted

No changes to the content.

migration/1fe00204f9d2cd653ff2894eae3d9356-4b3f45ce8aeb17ef18d1d9502b74bb7e/obelisk-upgradedeleted

No changes to the content.

migration/20926beb4c30e2efa819d23e5694645e-212ead9571b83b8164b3f18d2537d52b/obelisk-handoffdeleted

No changes to the content.

migration/20926beb4c30e2efa819d23e5694645e-212ead9571b83b8164b3f18d2537d52b/obelisk-upgradedeleted

No changes to the content.

migration/212ead9571b83b8164b3f18d2537d52b-3bf8e28473fb05791c5790f8906e2609/obelisk-handoffdeleted

No changes to the content.

migration/212ead9571b83b8164b3f18d2537d52b-3bf8e28473fb05791c5790f8906e2609/obelisk-upgradedeleted

No changes to the content.

migration/214979923f2d7e6278856493c1978fdc-13f6767460e94079cdfa6d5e1df79ecb/obelisk-handoffdeleted

No changes to the content.

migration/214979923f2d7e6278856493c1978fdc-13f6767460e94079cdfa6d5e1df79ecb/obelisk-upgradedeleted

No changes to the content.

migration/215b6810b09f219a5cb3842aacc88432-2ef4f9cce6d15ae62d5112d3c224eb7a/obelisk-handoffdeleted

No changes to the content.

migration/215b6810b09f219a5cb3842aacc88432-2ef4f9cce6d15ae62d5112d3c224eb7a/obelisk-upgradedeleted

No changes to the content.

migration/2164344be67da5d0e57052c15f30646d-0c9826e42d5d4fa9988c7db53722826e/obelisk-handoffdeleted

No changes to the content.

migration/2164344be67da5d0e57052c15f30646d-0c9826e42d5d4fa9988c7db53722826e/obelisk-upgradedeleted

No changes to the content.

migration/237aeb7eee0ffed230234ee04e49569f-55cf43fb0d3868040c9efbf2f92d538a/obelisk-handoffdeleted

No changes to the content.

migration/237aeb7eee0ffed230234ee04e49569f-55cf43fb0d3868040c9efbf2f92d538a/obelisk-upgradedeleted

No changes to the content.

migration/23af4d9671962484de53f537a0626d83-7ce32302cfbba8b7567a05144057f8a3/obelisk-handoffdeleted

No changes to the content.

migration/23af4d9671962484de53f537a0626d83-7ce32302cfbba8b7567a05144057f8a3/obelisk-upgradedeleted

No changes to the content.

migration/24e0d027f4160a6478c1356e51f419cf-4e3e6e8b9d0f4194842e530438ff686f/obelisk-handoffdeleted

No changes to the content.

migration/24e0d027f4160a6478c1356e51f419cf-4e3e6e8b9d0f4194842e530438ff686f/obelisk-upgradedeleted

No changes to the content.

migration/24f151a674662ac47845237d7a9f6a4f-4fe321cf196b9cd25210c35e09e09d8f/obelisk-handoffdeleted

No changes to the content.

migration/24f151a674662ac47845237d7a9f6a4f-4fe321cf196b9cd25210c35e09e09d8f/obelisk-upgradedeleted

No changes to the content.

migration/2543866165016a449562ea7a1ee36f6f-1a08b154ae5f898992128e229090c4d8/obelisk-handoffdeleted

No changes to the content.

migration/2543866165016a449562ea7a1ee36f6f-1a08b154ae5f898992128e229090c4d8/obelisk-upgradedeleted

No changes to the content.

migration/25615a274650370633e8083ed19fbdd0-80cb6b08443d55bb512e4a35b4768b72/obelisk-handoffdeleted

No changes to the content.

migration/25615a274650370633e8083ed19fbdd0-80cb6b08443d55bb512e4a35b4768b72/obelisk-upgradedeleted

No changes to the content.

migration/2600ab2ec96d3177d2a6b3462e09862d-e2b2d878b0ba6eb653b194523bf0a0e0/obelisk-handoffdeleted

No changes to the content.

migration/2600ab2ec96d3177d2a6b3462e09862d-e2b2d878b0ba6eb653b194523bf0a0e0/obelisk-upgradedeleted

No changes to the content.

migration/2619464637b85cb35376393fe612ddae-a8ebe201ab7a7b0211de41b14a95192e/obelisk-handoffdeleted

No changes to the content.

migration/2619464637b85cb35376393fe612ddae-a8ebe201ab7a7b0211de41b14a95192e/obelisk-upgradedeleted

No changes to the content.

migration/26a2491fc8a9658751c1511c777a67bd-470df6504156dc940b8e7f89c148e619/obelisk-handoffdeleted

No changes to the content.

migration/26a2491fc8a9658751c1511c777a67bd-470df6504156dc940b8e7f89c148e619/obelisk-upgradedeleted

No changes to the content.

migration/270b3b3f7bc510c692e3253a895f122a-c2d4eeb20f2b319a2d9dc5b6b0762f83/obelisk-handoffdeleted

No changes to the content.

migration/270b3b3f7bc510c692e3253a895f122a-c2d4eeb20f2b319a2d9dc5b6b0762f83/obelisk-upgradedeleted

No changes to the content.

migration/2773d4a82ca59b48faf036755e163a5f-71c6959cf98e05d356ff798ada6db2b8/obelisk-handoffdeleted

No changes to the content.

migration/2773d4a82ca59b48faf036755e163a5f-71c6959cf98e05d356ff798ada6db2b8/obelisk-upgradedeleted

No changes to the content.

migration/28a6ada033c86e5ae914cc31cecb6239-90b4e20464e08ade5f215f2a4ea28cc6/obelisk-handoffdeleted

No changes to the content.

migration/28a6ada033c86e5ae914cc31cecb6239-90b4e20464e08ade5f215f2a4ea28cc6/obelisk-upgradedeleted

No changes to the content.

migration/28c5fec3b7b0b14b3625d5c8f60bcf68-4509e2a908da9e497fa67acacdd107b6/obelisk-handoffdeleted

No changes to the content.

migration/28c5fec3b7b0b14b3625d5c8f60bcf68-4509e2a908da9e497fa67acacdd107b6/obelisk-upgradedeleted

No changes to the content.

migration/28e25b7a8812869a709579cd34e7bcb4-2543866165016a449562ea7a1ee36f6f/obelisk-handoffdeleted

No changes to the content.

migration/28e25b7a8812869a709579cd34e7bcb4-2543866165016a449562ea7a1ee36f6f/obelisk-upgradedeleted

No changes to the content.

migration/2913c440c7ad7731aae96e60a86bf7a8-a08aebb6f40fb45d174b229532314ac4/obelisk-handoffdeleted

No changes to the content.

migration/2913c440c7ad7731aae96e60a86bf7a8-a08aebb6f40fb45d174b229532314ac4/obelisk-upgradedeleted

No changes to the content.

migration/29abdd56a2dbbf3a2a0715210d441d06-51c7d001730ddd3d76e0c8bb57038773/obelisk-handoffdeleted

No changes to the content.

migration/29abdd56a2dbbf3a2a0715210d441d06-51c7d001730ddd3d76e0c8bb57038773/obelisk-upgradedeleted

No changes to the content.

migration/29d9b8b8ae9698888f3ea90b10ff5e92-1ada2d50d7847a12dbe29df4097b5bdc/obelisk-handoffdeleted

No changes to the content.

migration/29d9b8b8ae9698888f3ea90b10ff5e92-1ada2d50d7847a12dbe29df4097b5bdc/obelisk-upgradedeleted

No changes to the content.

migration/29fd07af89cc31526fca00226ff52a82-50b85e79010ad50ee275eaeba0df6f8d/obelisk-handoffdeleted

No changes to the content.

migration/29fd07af89cc31526fca00226ff52a82-50b85e79010ad50ee275eaeba0df6f8d/obelisk-upgradedeleted

No changes to the content.

migration/2ba356b187db8db13b8aa40f43ca025f-3c1fc501da00f467c9a48a1ddee2c2b0/obelisk-handoffdeleted

No changes to the content.

migration/2ba356b187db8db13b8aa40f43ca025f-3c1fc501da00f467c9a48a1ddee2c2b0/obelisk-upgradedeleted

No changes to the content.

migration/2bdd4221307cafa3e98c4d3017cdc912-5f6fe243beb9fcc0c4117536022fc14c/obelisk-handoffdeleted

No changes to the content.

migration/2bdd4221307cafa3e98c4d3017cdc912-5f6fe243beb9fcc0c4117536022fc14c/obelisk-upgradedeleted

No changes to the content.

migration/2c1521e5a5be64e6d28a9fdae950cd76-92ab733e8991f12cc12148f8d4e1b35d/obelisk-handoffdeleted

No changes to the content.

migration/2c1521e5a5be64e6d28a9fdae950cd76-92ab733e8991f12cc12148f8d4e1b35d/obelisk-upgradedeleted

No changes to the content.

migration/2ccf977797628dc3b1c002927fedc7a8-46f9c6c66e2bd8980402b6117ca40965/obelisk-handoffdeleted

No changes to the content.

migration/2ccf977797628dc3b1c002927fedc7a8-46f9c6c66e2bd8980402b6117ca40965/obelisk-upgradedeleted

No changes to the content.

migration/2df092178e167b474dd08dfa46ec3a49-9c94cd459a25b5c2de56067c8fdac620/obelisk-handoffdeleted

No changes to the content.

migration/2df092178e167b474dd08dfa46ec3a49-9c94cd459a25b5c2de56067c8fdac620/obelisk-upgradedeleted

No changes to the content.

migration/2e3e85cc59aa31f24f18c027542c0510-47357c6c23f049241902bf2e2e686ac8/obelisk-handoffdeleted

No changes to the content.

migration/2e3e85cc59aa31f24f18c027542c0510-47357c6c23f049241902bf2e2e686ac8/obelisk-upgradedeleted

No changes to the content.

migration/2e7cdc2bea08bb2358219ddefef372e0-a00870cfca85ff8dae5db3a78f5a227d/obelisk-handoffdeleted

No changes to the content.

migration/2e7cdc2bea08bb2358219ddefef372e0-a00870cfca85ff8dae5db3a78f5a227d/obelisk-upgradedeleted

No changes to the content.

migration/2e9a04a228106f62128ea5bd19d6b25e-947784563e3a27cf6486bed9a3c52f15/obelisk-handoffdeleted

No changes to the content.

migration/2e9a04a228106f62128ea5bd19d6b25e-947784563e3a27cf6486bed9a3c52f15/obelisk-upgradedeleted

No changes to the content.

migration/2eb907820012f77e60350430d89631fe-494be5819cd601fd2f0c20e1b23b84a3/obelisk-handoffdeleted

No changes to the content.

migration/2eb907820012f77e60350430d89631fe-494be5819cd601fd2f0c20e1b23b84a3/obelisk-upgradedeleted

No changes to the content.

migration/2ef4f9cce6d15ae62d5112d3c224eb7a-a080b8549e32f21fec11084f4ab1e052/obelisk-handoffdeleted

No changes to the content.

migration/2ef4f9cce6d15ae62d5112d3c224eb7a-a080b8549e32f21fec11084f4ab1e052/obelisk-upgradedeleted

No changes to the content.

migration/2f3ecb5cdb0ab839a49271d91dd7852a-f9ad863c0bebc103f8b81afdf82e6293/obelisk-handoffdeleted

No changes to the content.

migration/2f3ecb5cdb0ab839a49271d91dd7852a-f9ad863c0bebc103f8b81afdf82e6293/obelisk-upgradedeleted

No changes to the content.

migration/2f928f9c4a137186f808a231c9e3c141-9e61f37dd79310b71c80ee166aeb4e80/obelisk-handoffdeleted

No changes to the content.

migration/2f928f9c4a137186f808a231c9e3c141-9e61f37dd79310b71c80ee166aeb4e80/obelisk-upgradedeleted

No changes to the content.

migration/315cd79f97dae2f3669551dc03e0fd27-c801cbcf925c0a7b084ef152e3c08f8e/obelisk-handoffdeleted

No changes to the content.

migration/315cd79f97dae2f3669551dc03e0fd27-c801cbcf925c0a7b084ef152e3c08f8e/obelisk-upgradedeleted

No changes to the content.

migration/31a676fd9e1f69bd07380480ec879da0-9223532a537f698ef36f4dce6e922a6b/obelisk-handoffdeleted

No changes to the content.

migration/31a676fd9e1f69bd07380480ec879da0-9223532a537f698ef36f4dce6e922a6b/obelisk-upgradedeleted

No changes to the content.

migration/338b92f88c49c7c3a1d8ec231d9f7189-a4477746ae1a7024bc3cd0807180d418/obelisk-handoffdeleted

No changes to the content.

migration/338b92f88c49c7c3a1d8ec231d9f7189-a4477746ae1a7024bc3cd0807180d418/obelisk-upgradedeleted

No changes to the content.

migration/33da5c0e6705843afa6e39e65bfa0564-3ec562b7333839d48e9523aec595f3ab/obelisk-handoffdeleted

No changes to the content.

migration/33da5c0e6705843afa6e39e65bfa0564-3ec562b7333839d48e9523aec595f3ab/obelisk-upgradedeleted

No changes to the content.

migration/34d14d4a444d3bf1487ada6129815df4-afbd010add526d309dccee6895e42d0d/obelisk-handoffdeleted

No changes to the content.

migration/34d14d4a444d3bf1487ada6129815df4-afbd010add526d309dccee6895e42d0d/obelisk-upgradedeleted

No changes to the content.

migration/35acc1e0c4d9c02d60abd116f3df84ee-ffa62158ed6fdece012980e4ad25e6ac/obelisk-handoffdeleted

No changes to the content.

migration/35acc1e0c4d9c02d60abd116f3df84ee-ffa62158ed6fdece012980e4ad25e6ac/obelisk-upgradedeleted

No changes to the content.

migration/367616bd36eda757f251a5e8e9096acc-ddaa8e0e42a1ef00154d580a390c9f17/obelisk-handoffdeleted

No changes to the content.

migration/367616bd36eda757f251a5e8e9096acc-ddaa8e0e42a1ef00154d580a390c9f17/obelisk-upgradedeleted

No changes to the content.

migration/36cdb1c744c3036c2932f01d5fadd9f5-e7b566495114e160f1f4d722b7ed8174/obelisk-handoffdeleted

No changes to the content.

migration/36cdb1c744c3036c2932f01d5fadd9f5-e7b566495114e160f1f4d722b7ed8174/obelisk-upgradedeleted

No changes to the content.

migration/375acf7f2a3b3c487da2a6b30735d74d-3b614d291e262d0f281b54ff42a6bb66/obelisk-handoffdeleted

No changes to the content.

migration/375acf7f2a3b3c487da2a6b30735d74d-3b614d291e262d0f281b54ff42a6bb66/obelisk-upgradedeleted

No changes to the content.

migration/37c2ed0ba2cd54c0ec29eae9043bc4c8-190460dabd2b9773c3fe81902d0c8a62/obelisk-handoffdeleted

No changes to the content.

migration/37c2ed0ba2cd54c0ec29eae9043bc4c8-190460dabd2b9773c3fe81902d0c8a62/obelisk-upgradedeleted

No changes to the content.

migration/388d7d29a65c1713e07a654e91eb9caf-b6ffce85249f30616da76334c7bbec6e/obelisk-handoffdeleted

No changes to the content.

migration/388d7d29a65c1713e07a654e91eb9caf-b6ffce85249f30616da76334c7bbec6e/obelisk-upgradedeleted

No changes to the content.

migration/39df6544d6b035d4c77cc829729f9c19-9f6c8fd80ab3ff2dcc48bfd211c8b761/obelisk-handoffdeleted

No changes to the content.

migration/39df6544d6b035d4c77cc829729f9c19-9f6c8fd80ab3ff2dcc48bfd211c8b761/obelisk-upgradedeleted

No changes to the content.

migration/3a207630aa67e5df48d2b87d34db616b-8adb921f4dbb9d363bf6647d304e02fe/obelisk-handoffdeleted

No changes to the content.

migration/3a207630aa67e5df48d2b87d34db616b-8adb921f4dbb9d363bf6647d304e02fe/obelisk-upgradedeleted

No changes to the content.

migration/3b614d291e262d0f281b54ff42a6bb66-2e9a04a228106f62128ea5bd19d6b25e/obelisk-handoffdeleted

No changes to the content.

migration/3b614d291e262d0f281b54ff42a6bb66-2e9a04a228106f62128ea5bd19d6b25e/obelisk-upgradedeleted

No changes to the content.

migration/3bc53f3a28cb8f403cc41c11a37eec5d-cd86427cc2d5b2e931d7ab03deb19d20/obelisk-handoffdeleted

No changes to the content.

migration/3bc53f3a28cb8f403cc41c11a37eec5d-cd86427cc2d5b2e931d7ab03deb19d20/obelisk-upgradedeleted

No changes to the content.

migration/3bf8e28473fb05791c5790f8906e2609-45185a6f053557f93a13cb8cd03963a7/obelisk-handoffdeleted

No changes to the content.

migration/3bf8e28473fb05791c5790f8906e2609-45185a6f053557f93a13cb8cd03963a7/obelisk-upgradedeleted

No changes to the content.

migration/3c1fc501da00f467c9a48a1ddee2c2b0-cd5b54d21dd0fb868f48624d41021f47/obelisk-handoffdeleted

No changes to the content.

migration/3c1fc501da00f467c9a48a1ddee2c2b0-cd5b54d21dd0fb868f48624d41021f47/obelisk-upgradedeleted

No changes to the content.

migration/3c4b3152f0608d23d07240e2df3ff8da-c6baede3962454713b73acefcc92fe75/obelisk-handoffdeleted

No changes to the content.

migration/3c4b3152f0608d23d07240e2df3ff8da-c6baede3962454713b73acefcc92fe75/obelisk-upgradedeleted

No changes to the content.

migration/3c7755e495f20cfac5e4dabe78644c5a-51e25cf740f064faa0a29b8e2afcc109/obelisk-handoffdeleted

No changes to the content.

migration/3c7755e495f20cfac5e4dabe78644c5a-51e25cf740f064faa0a29b8e2afcc109/obelisk-upgradedeleted

No changes to the content.

migration/3c80eccfe80d0abd36b07bb11e69fc7a-b4f6596f2c16811442d8bb2961fbb721/obelisk-handoffdeleted

No changes to the content.

migration/3c80eccfe80d0abd36b07bb11e69fc7a-b4f6596f2c16811442d8bb2961fbb721/obelisk-upgradedeleted

No changes to the content.

migration/3cb52c2838303a362e9efb7c7eea37bd-aeafaa5d20bab4439a5d31f29023a839/obelisk-handoffdeleted

No changes to the content.

migration/3cb52c2838303a362e9efb7c7eea37bd-aeafaa5d20bab4439a5d31f29023a839/obelisk-upgradedeleted

No changes to the content.

migration/3da702ef7f531ad6db874ac3f85e9c06-73bcf3ed6593d074bd097cd43f5145d1/obelisk-handoffdeleted

No changes to the content.

migration/3da702ef7f531ad6db874ac3f85e9c06-73bcf3ed6593d074bd097cd43f5145d1/obelisk-upgradedeleted

No changes to the content.

migration/3da7d4a3b507d18097a90becf4dde9aa-e1ef432db54d9a6daa9bdeeb543262ae/obelisk-handoffdeleted

No changes to the content.

migration/3da7d4a3b507d18097a90becf4dde9aa-e1ef432db54d9a6daa9bdeeb543262ae/obelisk-upgradedeleted

No changes to the content.

migration/3daecb03985f83d8af466a9e0541782b-a2665cfd303a4a34a7bfa3b7d0d264bb/obelisk-handoffdeleted

No changes to the content.

migration/3daecb03985f83d8af466a9e0541782b-a2665cfd303a4a34a7bfa3b7d0d264bb/obelisk-upgradedeleted

No changes to the content.

migration/3e253c6beb567ba8f4c8086a76b70645-d6cacd7f4fab95c4b6aa2d6c1a4ad2a0/obelisk-handoffdeleted

No changes to the content.

migration/3e253c6beb567ba8f4c8086a76b70645-d6cacd7f4fab95c4b6aa2d6c1a4ad2a0/obelisk-upgradedeleted

No changes to the content.

migration/3e763ebe16cc3a261f5776ab1f1f6d9a-583ec22b9af66e8f629ee404307eb2dc/obelisk-handoffdeleted

No changes to the content.

migration/3e763ebe16cc3a261f5776ab1f1f6d9a-583ec22b9af66e8f629ee404307eb2dc/obelisk-upgradedeleted

No changes to the content.

migration/3e8a6968585d3d60f3b13652a386aec0-c118e1ee09ec8d9aa3edf04e211ae035/obelisk-handoffdeleted

No changes to the content.

migration/3e8a6968585d3d60f3b13652a386aec0-c118e1ee09ec8d9aa3edf04e211ae035/obelisk-upgradedeleted

No changes to the content.

migration/3ec562b7333839d48e9523aec595f3ab-7b9c28d09bd962e89b9715500f9e184c/obelisk-handoffdeleted

No changes to the content.

migration/3ec562b7333839d48e9523aec595f3ab-7b9c28d09bd962e89b9715500f9e184c/obelisk-upgradedeleted

No changes to the content.

migration/3f0687592a77c0c66193652e9cd981fd-fb81153791e49cd00850e91ff7418018/obelisk-handoffdeleted

No changes to the content.

migration/3f0687592a77c0c66193652e9cd981fd-fb81153791e49cd00850e91ff7418018/obelisk-upgradedeleted

No changes to the content.

migration/3f23549fba6f8e67ba36b4405e8680b3-a6633fc9687566aa84c3d5d5e2bff4fd/obelisk-handoffdeleted

No changes to the content.

migration/3f23549fba6f8e67ba36b4405e8680b3-a6633fc9687566aa84c3d5d5e2bff4fd/obelisk-upgradedeleted

No changes to the content.

migration/41e3ba178b727517aa1cedc8815abe86-1d084e187591225f0e8fa6fa824f8863/obelisk-handoffdeleted

No changes to the content.

migration/41e3ba178b727517aa1cedc8815abe86-1d084e187591225f0e8fa6fa824f8863/obelisk-upgradedeleted

No changes to the content.

migration/4205857df2e234a476ad56f84a33e6ed-db327f87e60d4e59bdbfd482e5abe985/obelisk-handoffdeleted

No changes to the content.

migration/4205857df2e234a476ad56f84a33e6ed-db327f87e60d4e59bdbfd482e5abe985/obelisk-upgradedeleted

No changes to the content.

migration/423088b98df50b986c9197af83c42a25-3a207630aa67e5df48d2b87d34db616b/obelisk-handoffdeleted

No changes to the content.

migration/423088b98df50b986c9197af83c42a25-3a207630aa67e5df48d2b87d34db616b/obelisk-upgradedeleted

No changes to the content.

migration/42357310c09e345f9360259c6c4102f2-ae8862f8b7292690c852111ca5659ce9/obelisk-handoffdeleted

No changes to the content.

migration/42357310c09e345f9360259c6c4102f2-ae8862f8b7292690c852111ca5659ce9/obelisk-upgradedeleted

No changes to the content.

migration/4344c1bd0ef04ebc055d5252f58b0bef-2f3ecb5cdb0ab839a49271d91dd7852a/obelisk-handoffdeleted

No changes to the content.

migration/4344c1bd0ef04ebc055d5252f58b0bef-2f3ecb5cdb0ab839a49271d91dd7852a/obelisk-upgradedeleted

No changes to the content.

migration/4509e2a908da9e497fa67acacdd107b6-1fe00204f9d2cd653ff2894eae3d9356/obelisk-handoffdeleted

No changes to the content.

migration/4509e2a908da9e497fa67acacdd107b6-1fe00204f9d2cd653ff2894eae3d9356/obelisk-upgradedeleted

No changes to the content.

migration/45185a6f053557f93a13cb8cd03963a7-cffcc6cab6080a35b7bf7c5c7facd6bc/obelisk-handoffdeleted

No changes to the content.

migration/45185a6f053557f93a13cb8cd03963a7-cffcc6cab6080a35b7bf7c5c7facd6bc/obelisk-upgradedeleted

No changes to the content.

migration/455279b6974b96a673613cd0ccc8ed54-b3ff195b0a90ef5900f9cef2ba75f2fe/obelisk-handoffdeleted

No changes to the content.

migration/455279b6974b96a673613cd0ccc8ed54-b3ff195b0a90ef5900f9cef2ba75f2fe/obelisk-upgradedeleted

No changes to the content.

migration/463646ba21b356f612539ea34178adf9-b3b107d406a6a7675cd5b9f52e5f7d8e/obelisk-handoffdeleted

No changes to the content.

migration/463646ba21b356f612539ea34178adf9-b3b107d406a6a7675cd5b9f52e5f7d8e/obelisk-upgradedeleted

No changes to the content.

migration/466a19f9cfa2a103467fe6627225d27f-c67f206b6d2626eb6d36b00122b52784/obelisk-handoffdeleted

No changes to the content.

migration/466a19f9cfa2a103467fe6627225d27f-c67f206b6d2626eb6d36b00122b52784/obelisk-upgradedeleted

No changes to the content.

migration/469a31d20d69da72e6a3a3ea2bb02f9d-75c9cd9763d0ed89c17d455f49d66a4f/obelisk-handoffdeleted

No changes to the content.

migration/469a31d20d69da72e6a3a3ea2bb02f9d-75c9cd9763d0ed89c17d455f49d66a4f/obelisk-upgradedeleted

No changes to the content.

migration/46aed07c1a61476dd245ff8e74c0559f-73e2573d9074884d7ae83106d55ad229/obelisk-handoffdeleted

No changes to the content.

migration/46aed07c1a61476dd245ff8e74c0559f-73e2573d9074884d7ae83106d55ad229/obelisk-upgradedeleted

No changes to the content.

migration/46f9c6c66e2bd8980402b6117ca40965-463646ba21b356f612539ea34178adf9/obelisk-handoffdeleted

No changes to the content.

migration/46f9c6c66e2bd8980402b6117ca40965-463646ba21b356f612539ea34178adf9/obelisk-upgradedeleted

No changes to the content.

migration/470df6504156dc940b8e7f89c148e619-a5297d8457aa4c327f35d4697f6cb66f/obelisk-handoffdeleted

No changes to the content.

migration/470df6504156dc940b8e7f89c148e619-a5297d8457aa4c327f35d4697f6cb66f/obelisk-upgradedeleted

No changes to the content.

migration/47357c6c23f049241902bf2e2e686ac8-d62d6d443d9c2261b7e09664398aa005/obelisk-handoffdeleted

No changes to the content.

migration/47357c6c23f049241902bf2e2e686ac8-d62d6d443d9c2261b7e09664398aa005/obelisk-upgradedeleted

No changes to the content.

migration/475f9a0d63e84e9cf13b8be3986cb603-b95c8d049256fb7360ca14b12acd8544/obelisk-handoffdeleted

No changes to the content.

migration/475f9a0d63e84e9cf13b8be3986cb603-b95c8d049256fb7360ca14b12acd8544/obelisk-upgradedeleted

No changes to the content.

migration/477a0bb12b48357d1e2b415c91d904e3-486d5218951605d14ec4a1046d3fe83e/obelisk-handoffdeleted

No changes to the content.

migration/477a0bb12b48357d1e2b415c91d904e3-486d5218951605d14ec4a1046d3fe83e/obelisk-upgradedeleted

No changes to the content.

migration/486d5218951605d14ec4a1046d3fe83e-90489c1a9f7edd46a070d6474dceac86/obelisk-handoffdeleted

No changes to the content.

migration/486d5218951605d14ec4a1046d3fe83e-90489c1a9f7edd46a070d6474dceac86/obelisk-upgradedeleted

No changes to the content.

migration/48c027da19d895ba03e7b9120f771291-29fd07af89cc31526fca00226ff52a82/obelisk-handoffdeleted

No changes to the content.

migration/48c027da19d895ba03e7b9120f771291-29fd07af89cc31526fca00226ff52a82/obelisk-upgradedeleted

No changes to the content.

migration/494be5819cd601fd2f0c20e1b23b84a3-c66bc5ba27dec52d144ceb0aa429e449/obelisk-handoffdeleted

No changes to the content.

migration/494be5819cd601fd2f0c20e1b23b84a3-c66bc5ba27dec52d144ceb0aa429e449/obelisk-upgradedeleted

No changes to the content.

migration/4a037879b8a542f1590cbb87b0242331-4344c1bd0ef04ebc055d5252f58b0bef/obelisk-handoffdeleted

No changes to the content.

migration/4a037879b8a542f1590cbb87b0242331-4344c1bd0ef04ebc055d5252f58b0bef/obelisk-upgradedeleted

No changes to the content.

migration/4ab915b764f38a1b0b4ae6f619f5c3fa-59e4a192c611a2a9f6736afde9fe1a0e/obelisk-handoffdeleted

No changes to the content.

migration/4ab915b764f38a1b0b4ae6f619f5c3fa-59e4a192c611a2a9f6736afde9fe1a0e/obelisk-upgradedeleted

No changes to the content.

migration/4ace4414d71235a291f7148f80a0b0f5-9705509619667aacf799e91927c16dc8/obelisk-handoffdeleted

No changes to the content.

migration/4ace4414d71235a291f7148f80a0b0f5-9705509619667aacf799e91927c16dc8/obelisk-upgradedeleted

No changes to the content.

migration/4aefa80392582ec03a19f946e752d331-cb98f93880e6dea74014976977d6eb93/obelisk-handoffdeleted

No changes to the content.

migration/4aefa80392582ec03a19f946e752d331-cb98f93880e6dea74014976977d6eb93/obelisk-upgradedeleted

No changes to the content.

migration/4b3f45ce8aeb17ef18d1d9502b74bb7e-4efbe4a73c260cca09fed7169e82a7ff/obelisk-handoffdeleted

No changes to the content.

migration/4b3f45ce8aeb17ef18d1d9502b74bb7e-4efbe4a73c260cca09fed7169e82a7ff/obelisk-upgradedeleted

No changes to the content.

migration/4b7a36dce2c1cf94554e12d8a7903269-dd6ca52a58ea6cda7b3eaa2e87bb13d9/obelisk-handoffdeleted

No changes to the content.

migration/4b7a36dce2c1cf94554e12d8a7903269-dd6ca52a58ea6cda7b3eaa2e87bb13d9/obelisk-upgradedeleted

No changes to the content.

migration/4c9372bf9aa6ebed1ea20abf3dbb0075-b641dd9c5359201200e483a784456cc2/obelisk-handoffdeleted

No changes to the content.

migration/4c9372bf9aa6ebed1ea20abf3dbb0075-b641dd9c5359201200e483a784456cc2/obelisk-upgradedeleted

No changes to the content.

migration/4d6197d8166de78f882376102e36300b-4205857df2e234a476ad56f84a33e6ed/obelisk-handoffdeleted

No changes to the content.

migration/4d6197d8166de78f882376102e36300b-4205857df2e234a476ad56f84a33e6ed/obelisk-upgradedeleted

No changes to the content.

migration/4d85ee8cdb0db20a5662b09567a2bbe7-b6425c7661af2116b912a558e9548013/obelisk-handoffdeleted

No changes to the content.

migration/4d85ee8cdb0db20a5662b09567a2bbe7-b6425c7661af2116b912a558e9548013/obelisk-upgradedeleted

No changes to the content.

migration/4da182e21be35feeee484dc85be7a075-14840816db4b5cfd5580ae099022604a/obelisk-handoffdeleted

No changes to the content.

migration/4da182e21be35feeee484dc85be7a075-14840816db4b5cfd5580ae099022604a/obelisk-upgradedeleted

No changes to the content.

migration/4db97ca86399c557e5ee0d04d0838acc-c203adc5fed9bf79f67df43d72a4631a/obelisk-handoffdeleted

No changes to the content.

migration/4db97ca86399c557e5ee0d04d0838acc-c203adc5fed9bf79f67df43d72a4631a/obelisk-upgradedeleted

No changes to the content.

migration/4e3e6e8b9d0f4194842e530438ff686f-b7e30314f9d0e69efdc84c7eb4e3bcc3/obelisk-handoffdeleted

No changes to the content.

migration/4e3e6e8b9d0f4194842e530438ff686f-b7e30314f9d0e69efdc84c7eb4e3bcc3/obelisk-upgradedeleted

No changes to the content.

migration/4e95e2f8ef50420371b71022c644e25d-8df9e256d8e43fa4e22d953024269b5b/obelisk-handoffdeleted

No changes to the content.

migration/4e95e2f8ef50420371b71022c644e25d-8df9e256d8e43fa4e22d953024269b5b/obelisk-upgradedeleted

No changes to the content.

migration/4efbe4a73c260cca09fed7169e82a7ff-315cd79f97dae2f3669551dc03e0fd27/obelisk-handoffdeleted

No changes to the content.

migration/4efbe4a73c260cca09fed7169e82a7ff-315cd79f97dae2f3669551dc03e0fd27/obelisk-upgradedeleted

No changes to the content.

migration/4fe321cf196b9cd25210c35e09e09d8f-56d7fd24c8a996e49bb26dd797819897/obelisk-handoffdeleted

No changes to the content.

migration/4fe321cf196b9cd25210c35e09e09d8f-56d7fd24c8a996e49bb26dd797819897/obelisk-upgradedeleted

No changes to the content.

migration/4ff5bf819ea43a638abbcfa071bbf3ef-975cd8245d06ad09e4fb176cfc9af5e0/obelisk-handoffdeleted

No changes to the content.

migration/4ff5bf819ea43a638abbcfa071bbf3ef-975cd8245d06ad09e4fb176cfc9af5e0/obelisk-upgradedeleted

No changes to the content.

migration/50b85e79010ad50ee275eaeba0df6f8d-e327773e1525eaa1eb2112dff8f41b26/obelisk-handoffdeleted

No changes to the content.

migration/50b85e79010ad50ee275eaeba0df6f8d-e327773e1525eaa1eb2112dff8f41b26/obelisk-upgradedeleted

No changes to the content.

migration/51c7d001730ddd3d76e0c8bb57038773-423088b98df50b986c9197af83c42a25/obelisk-handoffdeleted

No changes to the content.

migration/51c7d001730ddd3d76e0c8bb57038773-423088b98df50b986c9197af83c42a25/obelisk-upgradedeleted

No changes to the content.

migration/51e25cf740f064faa0a29b8e2afcc109-7bc4c944b24621967a6334f522437cc1/obelisk-handoffdeleted

No changes to the content.

migration/51e25cf740f064faa0a29b8e2afcc109-7bc4c944b24621967a6334f522437cc1/obelisk-upgradedeleted

No changes to the content.

migration/51f3d5068259f596b811450f0bf25419-f9716ad47c84e0b7ac7d426e8ad059f3/obelisk-handoffdeleted

No changes to the content.

migration/51f3d5068259f596b811450f0bf25419-f9716ad47c84e0b7ac7d426e8ad059f3/obelisk-upgradedeleted

No changes to the content.

migration/52330622ae996023b3f80a40991521a4-1df871cede0758cabe43971d9091279d/obelisk-handoffdeleted

No changes to the content.

migration/52330622ae996023b3f80a40991521a4-1df871cede0758cabe43971d9091279d/obelisk-upgradedeleted

No changes to the content.

migration/52df183d0f32d5a1e9ebe276b6d83d37-08dbdf23867a8efa680283ad251d3429/obelisk-handoffdeleted

No changes to the content.

migration/52df183d0f32d5a1e9ebe276b6d83d37-08dbdf23867a8efa680283ad251d3429/obelisk-upgradedeleted

No changes to the content.

migration/53d29061537ddaf7530aef88624ab266-9fe2922674fcee5e840c9f52cf534f88/obelisk-handoffdeleted

No changes to the content.

migration/53d29061537ddaf7530aef88624ab266-9fe2922674fcee5e840c9f52cf534f88/obelisk-upgradedeleted

No changes to the content.

migration/53fdf819333b86dfb485bf16a8f28db2-93fc625d41bae9fcf06b9febeacb686d/obelisk-handoffdeleted

No changes to the content.

migration/53fdf819333b86dfb485bf16a8f28db2-93fc625d41bae9fcf06b9febeacb686d/obelisk-upgradedeleted

No changes to the content.

migration/554c459ff2cced3e1b11241ed403890d-3543ea607791bc2e19419d22918823bd/obelisk-handoffdeleted

No changes to the content.

migration/554c459ff2cced3e1b11241ed403890d-3543ea607791bc2e19419d22918823bd/obelisk-upgradedeleted

No changes to the content.

migration/554c459ff2cced3e1b11241ed403890d-6a18939264224d6ce24c7426249edfea/obelisk-handoffdeleted

No changes to the content.

migration/554c459ff2cced3e1b11241ed403890d-6a18939264224d6ce24c7426249edfea/obelisk-upgradedeleted

No changes to the content.

migration/55cf43fb0d3868040c9efbf2f92d538a-e2706f337218a12a3bdddd18fa3f4cfd/obelisk-handoffdeleted

No changes to the content.

migration/55cf43fb0d3868040c9efbf2f92d538a-e2706f337218a12a3bdddd18fa3f4cfd/obelisk-upgradedeleted

No changes to the content.

migration/55d9c5eb3b86c9cb27966f849b2d32cf-0e4d0f0f0af0925474b0d772be2b145f/obelisk-handoffdeleted

No changes to the content.

migration/55d9c5eb3b86c9cb27966f849b2d32cf-0e4d0f0f0af0925474b0d772be2b145f/obelisk-upgradedeleted

No changes to the content.

migration/5631293b75143d170b8fca5579e15fee-0abd9d31c77be8c9a9e83bf1f73276f6/obelisk-handoffdeleted

No changes to the content.

migration/5631293b75143d170b8fca5579e15fee-0abd9d31c77be8c9a9e83bf1f73276f6/obelisk-upgradedeleted

No changes to the content.

migration/569227e5e7754c1890022850fd552292-ff1105290f89350bc9deb6eb907e0a53/obelisk-handoffdeleted

No changes to the content.

migration/569227e5e7754c1890022850fd552292-ff1105290f89350bc9deb6eb907e0a53/obelisk-upgradedeleted

No changes to the content.

migration/56d7fd24c8a996e49bb26dd797819897-61f734d4c59e46f03e92d2b86299c7c9/obelisk-handoffdeleted

No changes to the content.

migration/56d7fd24c8a996e49bb26dd797819897-61f734d4c59e46f03e92d2b86299c7c9/obelisk-upgradedeleted

No changes to the content.

migration/5734398c41e4eb5bb58d933ed2e12041-fb374b0721716a1e849e64daf6da73c4/obelisk-handoffdeleted

No changes to the content.

migration/5734398c41e4eb5bb58d933ed2e12041-fb374b0721716a1e849e64daf6da73c4/obelisk-upgradedeleted

No changes to the content.

migration/58391ba73c13d922e8a12116b28caad2-215b6810b09f219a5cb3842aacc88432/obelisk-handoffdeleted

No changes to the content.

migration/58391ba73c13d922e8a12116b28caad2-215b6810b09f219a5cb3842aacc88432/obelisk-upgradedeleted

No changes to the content.

migration/583ec22b9af66e8f629ee404307eb2dc-6c7f4af3693c31e1a2c6a2c3f189ddf5/obelisk-handoffdeleted

No changes to the content.

migration/583ec22b9af66e8f629ee404307eb2dc-6c7f4af3693c31e1a2c6a2c3f189ddf5/obelisk-upgradedeleted

No changes to the content.

migration/58479e482d3828ce44f2c8371ed9135a-174fa1072ee767be39b7b01aecea5714/obelisk-handoffdeleted

No changes to the content.

migration/58479e482d3828ce44f2c8371ed9135a-174fa1072ee767be39b7b01aecea5714/obelisk-upgradedeleted

No changes to the content.

migration/589eb89cdb09655c5edde4cd2a4deb80-c5a306045ad998f906f80748fe18487f/obelisk-handoffdeleted

No changes to the content.

migration/589eb89cdb09655c5edde4cd2a4deb80-c5a306045ad998f906f80748fe18487f/obelisk-upgradedeleted

No changes to the content.

migration/58f9d23d2cede2081dc6f6cfa2283f60-41e3ba178b727517aa1cedc8815abe86/obelisk-handoffdeleted

No changes to the content.

migration/58f9d23d2cede2081dc6f6cfa2283f60-41e3ba178b727517aa1cedc8815abe86/obelisk-upgradedeleted

No changes to the content.

migration/59742705e53838ae13066a2239a72040-95955d1dd0981ebb538ee941cde1f963/obelisk-handoffdeleted

No changes to the content.

migration/59742705e53838ae13066a2239a72040-95955d1dd0981ebb538ee941cde1f963/obelisk-upgradedeleted

No changes to the content.

migration/59e4a192c611a2a9f6736afde9fe1a0e-8143888349912db9780231c370ea2060/obelisk-handoffdeleted

No changes to the content.

migration/59e4a192c611a2a9f6736afde9fe1a0e-8143888349912db9780231c370ea2060/obelisk-upgradedeleted

No changes to the content.

migration/5a98cdb6c195e88beac878a3f311c5b5-66d79b4c393877a72aec18f227049207/obelisk-handoffdeleted

No changes to the content.

migration/5a98cdb6c195e88beac878a3f311c5b5-66d79b4c393877a72aec18f227049207/obelisk-upgradedeleted

No changes to the content.

migration/5b252c24612c2ba868580add9d9c19e3-6f104c322bbaff565981739406290402/obelisk-handoffdeleted

No changes to the content.

migration/5b252c24612c2ba868580add9d9c19e3-6f104c322bbaff565981739406290402/obelisk-upgradedeleted

No changes to the content.

migration/5b6770965bbef440db49b0703cd243a3-11f38b38c2d38004bb8a9dbfe2ef3e4a/obelisk-handoffdeleted

No changes to the content.

migration/5b6770965bbef440db49b0703cd243a3-11f38b38c2d38004bb8a9dbfe2ef3e4a/obelisk-upgradedeleted

No changes to the content.

migration/5cb98d0250bf5224b94e544a5140a54c-fa18f7522903a21b615dbee24c26bfbe/obelisk-handoffdeleted

No changes to the content.

migration/5cb98d0250bf5224b94e544a5140a54c-fa18f7522903a21b615dbee24c26bfbe/obelisk-upgradedeleted

No changes to the content.

migration/5d90abd974b4ec53091a8cb3fc2258ce-d40e0e3051a76c4c198cc11c2e8e4b7c/obelisk-handoffdeleted

No changes to the content.

migration/5d90abd974b4ec53091a8cb3fc2258ce-d40e0e3051a76c4c198cc11c2e8e4b7c/obelisk-upgradedeleted

No changes to the content.

migration/5f0d3f7480d67df7deebdb880726b356-4ab915b764f38a1b0b4ae6f619f5c3fa/obelisk-handoffdeleted

No changes to the content.

migration/5f0d3f7480d67df7deebdb880726b356-4ab915b764f38a1b0b4ae6f619f5c3fa/obelisk-upgradedeleted

No changes to the content.

migration/5f307639ccf6a64309166bfe58037397-702c65f35bc47641d60580d636ede330/obelisk-handoffdeleted

No changes to the content.

migration/5f307639ccf6a64309166bfe58037397-702c65f35bc47641d60580d636ede330/obelisk-upgradedeleted

No changes to the content.

migration/5f6fe243beb9fcc0c4117536022fc14c-f41b690c74938e347cf042cee2d4fc72/obelisk-handoffdeleted

No changes to the content.

migration/5f6fe243beb9fcc0c4117536022fc14c-f41b690c74938e347cf042cee2d4fc72/obelisk-upgradedeleted

No changes to the content.

migration/6095bdaccd933d319c84cde7983e2d10-477a0bb12b48357d1e2b415c91d904e3/obelisk-handoffdeleted

No changes to the content.

migration/6095bdaccd933d319c84cde7983e2d10-477a0bb12b48357d1e2b415c91d904e3/obelisk-upgradedeleted

No changes to the content.

migration/610dcf99f7eb8c8b591ff3f5f1722938-131e1d5cc8163ce9a174a8782be23fe7/obelisk-handoffdeleted

No changes to the content.

migration/610dcf99f7eb8c8b591ff3f5f1722938-131e1d5cc8163ce9a174a8782be23fe7/obelisk-upgradedeleted

No changes to the content.

migration/61f734d4c59e46f03e92d2b86299c7c9-02d0f8b5c7472e6f8a99ec2cfcb59807/obelisk-handoffdeleted

No changes to the content.

migration/61f734d4c59e46f03e92d2b86299c7c9-02d0f8b5c7472e6f8a99ec2cfcb59807/obelisk-upgradedeleted

No changes to the content.

migration/63422a9bade5c703461b600e03e227f0-01ddc6354698efa1ae5822da3be562dc/obelisk-handoffdeleted

No changes to the content.

migration/63422a9bade5c703461b600e03e227f0-01ddc6354698efa1ae5822da3be562dc/obelisk-upgradedeleted

No changes to the content.

migration/63ff9426130baa080f37400a86c55fdd-569227e5e7754c1890022850fd552292/obelisk-handoffdeleted

No changes to the content.

migration/63ff9426130baa080f37400a86c55fdd-569227e5e7754c1890022850fd552292/obelisk-upgradedeleted

No changes to the content.

migration/64ee9e40f4943e0122398e48019f5616-cd38e239c47fb92fabd867b7f86fc99a/obelisk-handoffdeleted

No changes to the content.

migration/64ee9e40f4943e0122398e48019f5616-cd38e239c47fb92fabd867b7f86fc99a/obelisk-upgradedeleted

No changes to the content.

migration/65071e255719af6389857646bc087d37-02fb88dab35884e09deef1721c1cfc13/obelisk-handoffdeleted

No changes to the content.

migration/65071e255719af6389857646bc087d37-02fb88dab35884e09deef1721c1cfc13/obelisk-upgradedeleted

No changes to the content.

migration/6558f8bda2aa17d76c509dde0a3dea56-2ccf977797628dc3b1c002927fedc7a8/obelisk-handoffdeleted

No changes to the content.

migration/6558f8bda2aa17d76c509dde0a3dea56-2ccf977797628dc3b1c002927fedc7a8/obelisk-upgradedeleted

No changes to the content.

migration/6566f0555737425bae9106d1a4a0a2dd-1f7fb90cb1e558f5285b2d19a25a1078/obelisk-handoffdeleted

No changes to the content.

migration/6566f0555737425bae9106d1a4a0a2dd-1f7fb90cb1e558f5285b2d19a25a1078/obelisk-upgradedeleted

No changes to the content.

migration/6620e6dc5622593e5f671127af9ade05-4ff5bf819ea43a638abbcfa071bbf3ef/obelisk-handoffdeleted

No changes to the content.

migration/6620e6dc5622593e5f671127af9ade05-4ff5bf819ea43a638abbcfa071bbf3ef/obelisk-upgradedeleted

No changes to the content.

migration/662eb30d6e4612f130edc3f6d37d92a2-94dd4eb45a4e7eb945027d5fd8eb4fa6/obelisk-handoffdeleted

No changes to the content.

migration/662eb30d6e4612f130edc3f6d37d92a2-94dd4eb45a4e7eb945027d5fd8eb4fa6/obelisk-upgradedeleted

No changes to the content.

migration/66a9b6b9e9bc4a5b0cfa78127479b231-88765caf54c2bf9f23ea4d05816a9f65/obelisk-handoffdeleted

No changes to the content.

migration/66a9b6b9e9bc4a5b0cfa78127479b231-88765caf54c2bf9f23ea4d05816a9f65/obelisk-upgradedeleted

No changes to the content.

migration/66d79b4c393877a72aec18f227049207-04dd95ecb889ac1b0a616ddfeec781c4/obelisk-handoffdeleted

No changes to the content.

migration/66d79b4c393877a72aec18f227049207-04dd95ecb889ac1b0a616ddfeec781c4/obelisk-upgradedeleted

No changes to the content.

migration/66f7a274206ad9f6d3c5701e4bcffc9d-2e7cdc2bea08bb2358219ddefef372e0/obelisk-handoffdeleted

No changes to the content.

migration/66f7a274206ad9f6d3c5701e4bcffc9d-2e7cdc2bea08bb2358219ddefef372e0/obelisk-upgradedeleted

No changes to the content.

migration/6708d14c71c475ac494808e75660a385-6095bdaccd933d319c84cde7983e2d10/obelisk-handoffdeleted

No changes to the content.

migration/6708d14c71c475ac494808e75660a385-6095bdaccd933d319c84cde7983e2d10/obelisk-upgradedeleted

No changes to the content.

migration/67173f110f2c54d135e6943b65118303-17089ea0edc0cf4b6f616936f8a06174/obelisk-handoffdeleted

No changes to the content.

migration/67173f110f2c54d135e6943b65118303-17089ea0edc0cf4b6f616936f8a06174/obelisk-upgradedeleted

No changes to the content.

migration/67bfa5b784a35e66875fb74ad84a38c0-f3ec62d09836804e39fad9d814137845/obelisk-handoffdeleted

No changes to the content.

migration/67bfa5b784a35e66875fb74ad84a38c0-f3ec62d09836804e39fad9d814137845/obelisk-upgradedeleted

No changes to the content.

migration/68cca18f813a16eb0cc2c3ed7fa08044-ee3dccc89ea6fdaaf9c1146b92861e90/obelisk-handoffdeleted

No changes to the content.

migration/68cca18f813a16eb0cc2c3ed7fa08044-ee3dccc89ea6fdaaf9c1146b92861e90/obelisk-upgradedeleted

No changes to the content.

migration/69537089a0e97845ffe05c73c3cc1536-e92786eaf56c32fd6b452c4a841d6317/obelisk-handoffdeleted

No changes to the content.

migration/69537089a0e97845ffe05c73c3cc1536-e92786eaf56c32fd6b452c4a841d6317/obelisk-upgradedeleted

No changes to the content.

migration/69cd775958e25e3b2cff7e4897f2a9bb-fca48edfdaa31b036176b97b04384682/obelisk-handoffdeleted

No changes to the content.

migration/69cd775958e25e3b2cff7e4897f2a9bb-fca48edfdaa31b036176b97b04384682/obelisk-upgradedeleted

No changes to the content.

migration/69fafe6ab155ff215b6d9d330f700ba1-6558f8bda2aa17d76c509dde0a3dea56/obelisk-handoffdeleted

No changes to the content.

migration/69fafe6ab155ff215b6d9d330f700ba1-6558f8bda2aa17d76c509dde0a3dea56/obelisk-upgradedeleted

No changes to the content.

migration/6a18939264224d6ce24c7426249edfea-e66afc8befac4d76b5780a960f768c31/obelisk-handoffdeleted

No changes to the content.

migration/6a18939264224d6ce24c7426249edfea-e66afc8befac4d76b5780a960f768c31/obelisk-upgradedeleted−15
@@ -1,15 +0,0 @@
1−# ob deploy: HTTPs support
2−
3−Obelisk deployments now support HTTPs. You must upgrade the deployment repo
4−associated with the obelisk project as follows:
5−
6−```
7−cd /your/project's/deployment/repo
8−echo True > enable_https
9−echo admin@yoursite.com > admin_email
10−echo https://yoursite.com/ > config/common/route
11−git add ... && git commit ...
12−ob deploy push
13−```
14−
15−If you do not wish to enable HTTPs, write `False` to `enable_https` instead.
migration/6c7f4af3693c31e1a2c6a2c3f189ddf5-388d7d29a65c1713e07a654e91eb9caf/obelisk-handoffdeleted

No changes to the content.

migration/6c7f4af3693c31e1a2c6a2c3f189ddf5-388d7d29a65c1713e07a654e91eb9caf/obelisk-upgradedeleted

No changes to the content.

migration/6da2e5b02ab780d12527640722d2b511-ca88f98cf9fb63d2feaef7fc19b30eca/obelisk-handoffdeleted

No changes to the content.

migration/6da2e5b02ab780d12527640722d2b511-ca88f98cf9fb63d2feaef7fc19b30eca/obelisk-upgradedeleted

No changes to the content.

migration/6dc299cb41f8977785a5d676cbcd32d7-237aeb7eee0ffed230234ee04e49569f/obelisk-handoffdeleted

No changes to the content.

migration/6dc299cb41f8977785a5d676cbcd32d7-237aeb7eee0ffed230234ee04e49569f/obelisk-upgradedeleted

No changes to the content.

migration/6f104c322bbaff565981739406290402-f98b82f5d142ce83733a3f18bdacb138/obelisk-handoffdeleted

No changes to the content.

migration/6f104c322bbaff565981739406290402-f98b82f5d142ce83733a3f18bdacb138/obelisk-upgradedeleted

No changes to the content.

migration/6f3a0f756847ee5aa208a1dc79404779-00c627540326314c7154cc209e8a71e9/obelisk-handoffdeleted

No changes to the content.

migration/6f3a0f756847ee5aa208a1dc79404779-00c627540326314c7154cc209e8a71e9/obelisk-upgradedeleted

No changes to the content.

migration/6fc44f180bb825d5b372444339681135-6dc299cb41f8977785a5d676cbcd32d7/obelisk-handoffdeleted

No changes to the content.

migration/6fc44f180bb825d5b372444339681135-6dc299cb41f8977785a5d676cbcd32d7/obelisk-upgradedeleted

No changes to the content.

migration/702c65f35bc47641d60580d636ede330-9217718d2b3b217dbba72e7091459548/obelisk-handoffdeleted

No changes to the content.

migration/702c65f35bc47641d60580d636ede330-9217718d2b3b217dbba72e7091459548/obelisk-upgradedeleted

No changes to the content.

migration/71a47d4d6856c1956c0d1c9de9af231f-bb9a87fc325204313d89dbfb689dba2f/obelisk-handoffdeleted

No changes to the content.

migration/71a47d4d6856c1956c0d1c9de9af231f-bb9a87fc325204313d89dbfb689dba2f/obelisk-upgradedeleted

No changes to the content.

migration/71c6959cf98e05d356ff798ada6db2b8-55d9c5eb3b86c9cb27966f849b2d32cf/obelisk-handoffdeleted

No changes to the content.

migration/71c6959cf98e05d356ff798ada6db2b8-55d9c5eb3b86c9cb27966f849b2d32cf/obelisk-upgradedeleted

No changes to the content.

migration/71da39c91abb23488021a1e9142507d5-16b24e1a12b93ea83d7656a09d5e3338/obelisk-handoffdeleted

No changes to the content.

migration/71da39c91abb23488021a1e9142507d5-16b24e1a12b93ea83d7656a09d5e3338/obelisk-upgradedeleted

No changes to the content.

migration/721a02f3a7549e7d6efeaed087bfacb5-52df183d0f32d5a1e9ebe276b6d83d37/obelisk-handoffdeleted

No changes to the content.

migration/721a02f3a7549e7d6efeaed087bfacb5-52df183d0f32d5a1e9ebe276b6d83d37/obelisk-upgradedeleted

No changes to the content.

migration/729aaf942e685bb37fdbd5e97293bbcb-7b2b9cefa7dd57d51b587c3501a1b9ef/obelisk-handoffdeleted

No changes to the content.

migration/729aaf942e685bb37fdbd5e97293bbcb-7b2b9cefa7dd57d51b587c3501a1b9ef/obelisk-upgradedeleted

No changes to the content.

migration/73bcf3ed6593d074bd097cd43f5145d1-375acf7f2a3b3c487da2a6b30735d74d/obelisk-handoffdeleted

No changes to the content.

migration/73bcf3ed6593d074bd097cd43f5145d1-375acf7f2a3b3c487da2a6b30735d74d/obelisk-upgradedeleted

No changes to the content.

migration/73e2573d9074884d7ae83106d55ad229-729aaf942e685bb37fdbd5e97293bbcb/obelisk-handoffdeleted

No changes to the content.

migration/73e2573d9074884d7ae83106d55ad229-729aaf942e685bb37fdbd5e97293bbcb/obelisk-upgradedeleted

No changes to the content.

migration/7447d29f4712c72458c049cdb9380753-e24a771ee59f24e45afc0a27307f2070/obelisk-handoffdeleted

No changes to the content.

migration/7447d29f4712c72458c049cdb9380753-e24a771ee59f24e45afc0a27307f2070/obelisk-upgradedeleted

No changes to the content.

migration/749df832c9305e1c6309a794b0c1b201-2619464637b85cb35376393fe612ddae/obelisk-handoffdeleted

No changes to the content.

migration/749df832c9305e1c6309a794b0c1b201-2619464637b85cb35376393fe612ddae/obelisk-upgradedeleted

No changes to the content.

migration/75c9cd9763d0ed89c17d455f49d66a4f-94372bb14f8afd19bce24f8402c7564c/obelisk-handoffdeleted

No changes to the content.

migration/75c9cd9763d0ed89c17d455f49d66a4f-94372bb14f8afd19bce24f8402c7564c/obelisk-upgradedeleted

No changes to the content.

migration/77a721a8fa97d1f84dd2af622089871b-cb2d040cc16752b1dc89eecf200ea798/obelisk-handoffdeleted

No changes to the content.

migration/77a721a8fa97d1f84dd2af622089871b-cb2d040cc16752b1dc89eecf200ea798/obelisk-upgradedeleted

No changes to the content.

migration/79357758056556793db0472562d62ff9-f15c9c2f894c8cc4fac07e2cdef47180/obelisk-handoffdeleted

No changes to the content.

migration/79357758056556793db0472562d62ff9-f15c9c2f894c8cc4fac07e2cdef47180/obelisk-upgradedeleted

No changes to the content.

migration/7adece557941a7d7ac7c150f77f758be-67173f110f2c54d135e6943b65118303/obelisk-handoffdeleted

No changes to the content.

migration/7adece557941a7d7ac7c150f77f758be-67173f110f2c54d135e6943b65118303/obelisk-upgradedeleted

No changes to the content.

migration/7b2b9cefa7dd57d51b587c3501a1b9ef-1269155fffe21926f3704d5077e04332/obelisk-handoffdeleted

No changes to the content.

migration/7b2b9cefa7dd57d51b587c3501a1b9ef-1269155fffe21926f3704d5077e04332/obelisk-upgradedeleted

No changes to the content.

migration/7b75ffdcd86b5a330d9c325ad5767393-97873167212295a0fdc1219102b6b600/obelisk-handoffdeleted

No changes to the content.

migration/7b75ffdcd86b5a330d9c325ad5767393-97873167212295a0fdc1219102b6b600/obelisk-upgradedeleted

No changes to the content.

migration/7b9c28d09bd962e89b9715500f9e184c-df71cd82e801ca701bf4bc44b80b6d50/obelisk-handoffdeleted

No changes to the content.

migration/7b9c28d09bd962e89b9715500f9e184c-df71cd82e801ca701bf4bc44b80b6d50/obelisk-upgradedeleted

No changes to the content.

migration/7bc4c944b24621967a6334f522437cc1-3e8a6968585d3d60f3b13652a386aec0/obelisk-handoffdeleted

No changes to the content.

migration/7bc4c944b24621967a6334f522437cc1-3e8a6968585d3d60f3b13652a386aec0/obelisk-upgradedeleted

No changes to the content.

migration/7c0ab1210424b615251ef57036d316c3-b8aa42d4f2e5ef7db9ba34ecf6c42079/obelisk-handoffdeleted

No changes to the content.

migration/7c0ab1210424b615251ef57036d316c3-b8aa42d4f2e5ef7db9ba34ecf6c42079/obelisk-upgradedeleted

No changes to the content.

migration/7c98ef4a123c1a580d368d14e54961b7-adad34a1075a2fd51e1a1c06ec9d002d/obelisk-handoffdeleted

No changes to the content.

migration/7c98ef4a123c1a580d368d14e54961b7-adad34a1075a2fd51e1a1c06ec9d002d/obelisk-upgradedeleted

No changes to the content.

migration/7ce32302cfbba8b7567a05144057f8a3-28c5fec3b7b0b14b3625d5c8f60bcf68/obelisk-handoffdeleted

No changes to the content.

migration/7ce32302cfbba8b7567a05144057f8a3-28c5fec3b7b0b14b3625d5c8f60bcf68/obelisk-upgradedeleted

No changes to the content.

migration/7cf814f7404638f1c1be662a24d7cfea-e609d945b642fd9d4ce99931e3699db7/obelisk-handoffdeleted

No changes to the content.

migration/7cf814f7404638f1c1be662a24d7cfea-e609d945b642fd9d4ce99931e3699db7/obelisk-upgradedeleted

No changes to the content.

migration/7dd60550d7d79bd3923d91d67ecb433a-46aed07c1a61476dd245ff8e74c0559f/obelisk-handoffdeleted

No changes to the content.

migration/7dd60550d7d79bd3923d91d67ecb433a-46aed07c1a61476dd245ff8e74c0559f/obelisk-upgradedeleted

No changes to the content.

migration/7f46d84c4d303f30602b920f96acf871-97c7e1ea2c1ddb0ef23d2e4a9279f65c/obelisk-handoffdeleted

No changes to the content.

migration/7f46d84c4d303f30602b920f96acf871-97c7e1ea2c1ddb0ef23d2e4a9279f65c/obelisk-upgradedeleted

No changes to the content.

migration/7fac40a2398039b364746989bb78798b-68cca18f813a16eb0cc2c3ed7fa08044/obelisk-handoffdeleted

No changes to the content.

migration/7fac40a2398039b364746989bb78798b-68cca18f813a16eb0cc2c3ed7fa08044/obelisk-upgradedeleted

No changes to the content.

migration/7fc381538805adac19c54b73ea118601-982c57eada4652019d0b0cf6ad355d25/obelisk-handoffdeleted

No changes to the content.

migration/7fc381538805adac19c54b73ea118601-982c57eada4652019d0b0cf6ad355d25/obelisk-upgradedeleted

No changes to the content.

migration/80cb6b08443d55bb512e4a35b4768b72-59742705e53838ae13066a2239a72040/obelisk-handoffdeleted

No changes to the content.

migration/80cb6b08443d55bb512e4a35b4768b72-59742705e53838ae13066a2239a72040/obelisk-upgradedeleted

No changes to the content.

migration/81223455459db28cdbc990c3d4e6eedd-71da39c91abb23488021a1e9142507d5/obelisk-handoffdeleted

No changes to the content.

migration/81223455459db28cdbc990c3d4e6eedd-71da39c91abb23488021a1e9142507d5/obelisk-upgradedeleted

No changes to the content.

migration/8143888349912db9780231c370ea2060-beaa30317dadbef830531237279cbca4/obelisk-handoffdeleted

No changes to the content.

migration/8143888349912db9780231c370ea2060-beaa30317dadbef830531237279cbca4/obelisk-upgradedeleted

No changes to the content.

migration/81f3dbeef0d78bfb523cd2ac113b8ad8-4ace4414d71235a291f7148f80a0b0f5/obelisk-handoffdeleted

No changes to the content.

migration/81f3dbeef0d78bfb523cd2ac113b8ad8-4ace4414d71235a291f7148f80a0b0f5/obelisk-upgradedeleted

No changes to the content.

migration/84f4d5c5491abb855d8dbe3acf3bc8e6-01fb7610e4184ad1b491267fcb0c7f43/obelisk-handoffdeleted

No changes to the content.

migration/84f4d5c5491abb855d8dbe3acf3bc8e6-01fb7610e4184ad1b491267fcb0c7f43/obelisk-upgradedeleted

No changes to the content.

migration/85c2c4fb8472d50eaa97aed0bdc9b142-3e253c6beb567ba8f4c8086a76b70645/obelisk-handoffdeleted

No changes to the content.

migration/85c2c4fb8472d50eaa97aed0bdc9b142-3e253c6beb567ba8f4c8086a76b70645/obelisk-upgradedeleted

No changes to the content.

migration/8746b8f08b75b2bb7c22d8f17f22d72d-e3b3014d7e0dc983fe118d32546654e5/obelisk-handoffdeleted

No changes to the content.

migration/8746b8f08b75b2bb7c22d8f17f22d72d-e3b3014d7e0dc983fe118d32546654e5/obelisk-upgradedeleted

No changes to the content.

migration/88046de6fbbdad3f6a8a5d311948ff9b-4a037879b8a542f1590cbb87b0242331/obelisk-handoffdeleted

No changes to the content.

migration/88046de6fbbdad3f6a8a5d311948ff9b-4a037879b8a542f1590cbb87b0242331/obelisk-upgradedeleted

No changes to the content.

migration/88439725720c7ba784750cda91157dfd-69cd775958e25e3b2cff7e4897f2a9bb/obelisk-handoffdeleted

No changes to the content.

migration/88439725720c7ba784750cda91157dfd-69cd775958e25e3b2cff7e4897f2a9bb/obelisk-upgradedeleted

No changes to the content.

migration/88765caf54c2bf9f23ea4d05816a9f65-52330622ae996023b3f80a40991521a4/obelisk-handoffdeleted

No changes to the content.

migration/88765caf54c2bf9f23ea4d05816a9f65-52330622ae996023b3f80a40991521a4/obelisk-upgradedeleted

No changes to the content.

migration/8911be694899014078c8e79fc858be45-33da5c0e6705843afa6e39e65bfa0564/obelisk-handoffdeleted

No changes to the content.

migration/8911be694899014078c8e79fc858be45-33da5c0e6705843afa6e39e65bfa0564/obelisk-upgradedeleted

No changes to the content.

migration/8a64ec5f2ee43d69607499f085560857-cba28ee4b736cf8ed43cb342ccaef12e/obelisk-handoffdeleted

No changes to the content.

migration/8a64ec5f2ee43d69607499f085560857-cba28ee4b736cf8ed43cb342ccaef12e/obelisk-upgradedeleted

No changes to the content.

migration/8adb921f4dbb9d363bf6647d304e02fe-bb2f504aa4f6f1ab8ed75697dc773efb/obelisk-handoffdeleted

No changes to the content.

migration/8adb921f4dbb9d363bf6647d304e02fe-bb2f504aa4f6f1ab8ed75697dc773efb/obelisk-upgradedeleted

No changes to the content.

migration/8b098f49e1fd1aa10ca22b9dc312644e-0ec6f67d94d3913cf3243d813238ec39/obelisk-handoffdeleted

No changes to the content.

migration/8b098f49e1fd1aa10ca22b9dc312644e-0ec6f67d94d3913cf3243d813238ec39/obelisk-upgradedeleted

No changes to the content.

migration/8c7718f94e2676c957fe844b7dc2637c-96c3d49b08a9d2cfb059197c60f49c05/obelisk-handoffdeleted

No changes to the content.

migration/8c7718f94e2676c957fe844b7dc2637c-96c3d49b08a9d2cfb059197c60f49c05/obelisk-upgradedeleted

No changes to the content.

migration/8c9599be2461288eee7e78d7a34643d8-053c0b2f84a7a2eadeb5c3f9575ffa0d/obelisk-handoffdeleted

No changes to the content.

migration/8c9599be2461288eee7e78d7a34643d8-053c0b2f84a7a2eadeb5c3f9575ffa0d/obelisk-upgradedeleted

No changes to the content.

migration/8d5dba70e93ae7e784ea238281842408-c93529e579e64263fd91663216fd3fe7/obelisk-handoffdeleted

No changes to the content.

migration/8d5dba70e93ae7e784ea238281842408-c93529e579e64263fd91663216fd3fe7/obelisk-upgradedeleted

No changes to the content.

migration/8dcf46725e839f4a5dcb7a9f0235e01b-0da1150f0e9fe57d1fafbb6cabe24fa1/obelisk-handoffdeleted

No changes to the content.

migration/8dcf46725e839f4a5dcb7a9f0235e01b-0da1150f0e9fe57d1fafbb6cabe24fa1/obelisk-upgradedeleted

No changes to the content.

migration/8df9e256d8e43fa4e22d953024269b5b-9d86cedf551a6ee24c5ae6779021b925/obelisk-handoffdeleted

No changes to the content.

migration/8df9e256d8e43fa4e22d953024269b5b-9d86cedf551a6ee24c5ae6779021b925/obelisk-upgradedeleted

No changes to the content.

migration/8e01c7fee83aa74229a77772d8c928f3-8911be694899014078c8e79fc858be45/obelisk-handoffdeleted

No changes to the content.

migration/8e01c7fee83aa74229a77772d8c928f3-8911be694899014078c8e79fc858be45/obelisk-upgradedeleted

No changes to the content.

migration/8e1e426afe242ea3079f160bccd94cf0-610dcf99f7eb8c8b591ff3f5f1722938/obelisk-handoffdeleted

No changes to the content.

migration/8e1e426afe242ea3079f160bccd94cf0-610dcf99f7eb8c8b591ff3f5f1722938/obelisk-upgradedeleted

No changes to the content.

migration/8fca2c4d6478ed8628925852b2af45bf-03b845f308e73a1567eb843dfca182d2/obelisk-handoffdeleted

No changes to the content.

migration/8fca2c4d6478ed8628925852b2af45bf-03b845f308e73a1567eb843dfca182d2/obelisk-upgradedeleted

No changes to the content.

migration/90489c1a9f7edd46a070d6474dceac86-7fac40a2398039b364746989bb78798b/obelisk-handoffdeleted

No changes to the content.

migration/90489c1a9f7edd46a070d6474dceac86-7fac40a2398039b364746989bb78798b/obelisk-upgradedeleted

No changes to the content.

migration/909b6b2066cbdc991e6c16455f13637c-1e36dc8817e2784df36d643b7499092d/obelisk-handoffdeleted

No changes to the content.

migration/909b6b2066cbdc991e6c16455f13637c-1e36dc8817e2784df36d643b7499092d/obelisk-upgradedeleted

No changes to the content.

migration/90b4e20464e08ade5f215f2a4ea28cc6-b7630e5f30252987bf899c74de3953ad/obelisk-handoffdeleted

No changes to the content.

migration/90b4e20464e08ade5f215f2a4ea28cc6-b7630e5f30252987bf899c74de3953ad/obelisk-upgradedeleted

No changes to the content.

migration/9217718d2b3b217dbba72e7091459548-88046de6fbbdad3f6a8a5d311948ff9b/obelisk-handoffdeleted

No changes to the content.

migration/9217718d2b3b217dbba72e7091459548-88046de6fbbdad3f6a8a5d311948ff9b/obelisk-upgradedeleted

No changes to the content.

migration/9223532a537f698ef36f4dce6e922a6b-d327ceb69d24e40dc11792840454cff9/obelisk-handoffdeleted

No changes to the content.

migration/9223532a537f698ef36f4dce6e922a6b-d327ceb69d24e40dc11792840454cff9/obelisk-upgradedeleted

No changes to the content.

migration/92ab733e8991f12cc12148f8d4e1b35d-e4b89dd259f3831bc60b56147b3586a6/obelisk-handoffdeleted

No changes to the content.

migration/92ab733e8991f12cc12148f8d4e1b35d-e4b89dd259f3831bc60b56147b3586a6/obelisk-upgradedeleted

No changes to the content.

migration/930e9213e072e87c207f93461bd3c96c-7f46d84c4d303f30602b920f96acf871/obelisk-handoffdeleted

No changes to the content.

migration/930e9213e072e87c207f93461bd3c96c-7f46d84c4d303f30602b920f96acf871/obelisk-upgradedeleted

No changes to the content.

migration/93f164bbd887f688fd49bb80afffccd6-f8eee9431c8b7a767d96da0a8cfae95d/obelisk-handoffdeleted

No changes to the content.

migration/93f164bbd887f688fd49bb80afffccd6-f8eee9431c8b7a767d96da0a8cfae95d/obelisk-upgradedeleted

No changes to the content.

migration/93fc625d41bae9fcf06b9febeacb686d-662eb30d6e4612f130edc3f6d37d92a2/obelisk-handoffdeleted

No changes to the content.

migration/93fc625d41bae9fcf06b9febeacb686d-662eb30d6e4612f130edc3f6d37d92a2/obelisk-upgradedeleted

No changes to the content.

migration/94252585266fa745a91733888e2f50b7-d207ab99328b8ece5fb27fa1f4dbf9f6/obelisk-handoffdeleted

No changes to the content.

migration/94252585266fa745a91733888e2f50b7-d207ab99328b8ece5fb27fa1f4dbf9f6/obelisk-upgradedeleted

No changes to the content.

migration/94372bb14f8afd19bce24f8402c7564c-7dd60550d7d79bd3923d91d67ecb433a/obelisk-handoffdeleted

No changes to the content.

migration/94372bb14f8afd19bce24f8402c7564c-7dd60550d7d79bd3923d91d67ecb433a/obelisk-upgradedeleted

No changes to the content.

migration/947784563e3a27cf6486bed9a3c52f15-25615a274650370633e8083ed19fbdd0/obelisk-handoffdeleted

No changes to the content.

migration/947784563e3a27cf6486bed9a3c52f15-25615a274650370633e8083ed19fbdd0/obelisk-upgradedeleted

No changes to the content.

migration/94dd4eb45a4e7eb945027d5fd8eb4fa6-e22c51145e08d4a089003460ae80a7f9/obelisk-handoffdeleted

No changes to the content.

migration/94dd4eb45a4e7eb945027d5fd8eb4fa6-e22c51145e08d4a089003460ae80a7f9/obelisk-upgradedeleted

No changes to the content.

migration/95955d1dd0981ebb538ee941cde1f963-07900e6c41ab9a78917f66fcf7e0d9b8/obelisk-handoffdeleted

No changes to the content.

migration/95955d1dd0981ebb538ee941cde1f963-07900e6c41ab9a78917f66fcf7e0d9b8/obelisk-upgradedeleted

No changes to the content.

migration/959ae2925bcd2b5beff631a8e7aeaf00-071f5b3326b8886de3eddc927b80eb10/obelisk-handoffdeleted

No changes to the content.

migration/959ae2925bcd2b5beff631a8e7aeaf00-071f5b3326b8886de3eddc927b80eb10/obelisk-upgradedeleted

No changes to the content.

migration/962e2bd1ae8d0c233f8da89ecebffe50-d6c090699acd25fc60213ff4bd9eebcf/obelisk-handoffdeleted

No changes to the content.

migration/962e2bd1ae8d0c233f8da89ecebffe50-d6c090699acd25fc60213ff4bd9eebcf/obelisk-upgradedeleted

No changes to the content.

migration/96c3d49b08a9d2cfb059197c60f49c05-6da2e5b02ab780d12527640722d2b511/obelisk-handoffdeleted

No changes to the content.

migration/96c3d49b08a9d2cfb059197c60f49c05-6da2e5b02ab780d12527640722d2b511/obelisk-upgradedeleted

No changes to the content.

migration/9705509619667aacf799e91927c16dc8-2773d4a82ca59b48faf036755e163a5f/obelisk-handoffdeleted

No changes to the content.

migration/9705509619667aacf799e91927c16dc8-2773d4a82ca59b48faf036755e163a5f/obelisk-upgradedeleted

No changes to the content.

migration/9715967793be68dfd8c9a7d631f8d589-d532356dfb65a4c8a39452d2ebca2516/obelisk-handoffdeleted

No changes to the content.

migration/9715967793be68dfd8c9a7d631f8d589-d532356dfb65a4c8a39452d2ebca2516/obelisk-upgradedeleted

No changes to the content.

migration/975cd8245d06ad09e4fb176cfc9af5e0-3f23549fba6f8e67ba36b4405e8680b3/obelisk-handoffdeleted

No changes to the content.

migration/975cd8245d06ad09e4fb176cfc9af5e0-3f23549fba6f8e67ba36b4405e8680b3/obelisk-upgradedeleted

No changes to the content.

migration/97873167212295a0fdc1219102b6b600-88439725720c7ba784750cda91157dfd/obelisk-handoffdeleted

No changes to the content.

migration/97873167212295a0fdc1219102b6b600-88439725720c7ba784750cda91157dfd/obelisk-upgradedeleted

No changes to the content.

migration/97c7e1ea2c1ddb0ef23d2e4a9279f65c-d747c3d0620b2034aca44265dd1c8c86/obelisk-handoffdeleted

No changes to the content.

migration/97c7e1ea2c1ddb0ef23d2e4a9279f65c-d747c3d0620b2034aca44265dd1c8c86/obelisk-upgradedeleted

No changes to the content.

migration/982c57eada4652019d0b0cf6ad355d25-6708d14c71c475ac494808e75660a385/obelisk-handoffdeleted

No changes to the content.

migration/982c57eada4652019d0b0cf6ad355d25-6708d14c71c475ac494808e75660a385/obelisk-upgradedeleted

No changes to the content.

migration/98629a336dcd5fa3524a09f8ea6ad636-8c9599be2461288eee7e78d7a34643d8/obelisk-handoffdeleted

No changes to the content.

migration/98629a336dcd5fa3524a09f8ea6ad636-8c9599be2461288eee7e78d7a34643d8/obelisk-upgradedeleted

No changes to the content.

migration/98da197f133bca81e533ab5ffbf4c82d-a0b418c582d5c6ef9e0cd9a19abe046e/obelisk-handoffdeleted

No changes to the content.

migration/98da197f133bca81e533ab5ffbf4c82d-a0b418c582d5c6ef9e0cd9a19abe046e/obelisk-upgradedeleted

No changes to the content.

migration/98f9e0bbd21e36b0f4daa440d11e7177-bab3871bcfbfcb3d2bad4495d44ae4f8/obelisk-handoffdeleted

No changes to the content.

migration/98f9e0bbd21e36b0f4daa440d11e7177-bab3871bcfbfcb3d2bad4495d44ae4f8/obelisk-upgradedeleted

No changes to the content.

migration/99aebbfcac51725464bc59571b7d71b8-34d14d4a444d3bf1487ada6129815df4/obelisk-handoffdeleted

No changes to the content.

migration/99aebbfcac51725464bc59571b7d71b8-34d14d4a444d3bf1487ada6129815df4/obelisk-upgradedeleted

No changes to the content.

migration/9b33a49f0c978e27b752fa457aca97cc-51f3d5068259f596b811450f0bf25419/obelisk-handoffdeleted

No changes to the content.

migration/9b33a49f0c978e27b752fa457aca97cc-51f3d5068259f596b811450f0bf25419/obelisk-upgradedeleted

No changes to the content.

migration/9b9d73736773550dde7d36de2498b029-69537089a0e97845ffe05c73c3cc1536/obelisk-handoffdeleted

No changes to the content.

migration/9b9d73736773550dde7d36de2498b029-69537089a0e97845ffe05c73c3cc1536/obelisk-upgradedeleted

No changes to the content.

migration/9c94cd459a25b5c2de56067c8fdac620-721a02f3a7549e7d6efeaed087bfacb5/obelisk-handoffdeleted

No changes to the content.

migration/9c94cd459a25b5c2de56067c8fdac620-721a02f3a7549e7d6efeaed087bfacb5/obelisk-upgradedeleted

No changes to the content.

migration/9cef2dd1885f16455ff9b6801ec99454-63ff9426130baa080f37400a86c55fdd/obelisk-handoffdeleted

No changes to the content.

migration/9cef2dd1885f16455ff9b6801ec99454-63ff9426130baa080f37400a86c55fdd/obelisk-upgradedeleted

No changes to the content.

migration/9d86cedf551a6ee24c5ae6779021b925-1c9bdb5c544f2e5432f549653a61dabe/obelisk-handoffdeleted

No changes to the content.

migration/9d86cedf551a6ee24c5ae6779021b925-1c9bdb5c544f2e5432f549653a61dabe/obelisk-upgradedeleted

No changes to the content.

migration/9e05cc6b81c6380ba0ac59bad1021cac-ce7f0d15d71d36d4bcf4fbadcdfa1b58/obelisk-handoffdeleted

No changes to the content.

migration/9e05cc6b81c6380ba0ac59bad1021cac-ce7f0d15d71d36d4bcf4fbadcdfa1b58/obelisk-upgradedeleted

No changes to the content.

migration/9e61f37dd79310b71c80ee166aeb4e80-466a19f9cfa2a103467fe6627225d27f/obelisk-handoffdeleted

No changes to the content.

migration/9e61f37dd79310b71c80ee166aeb4e80-466a19f9cfa2a103467fe6627225d27f/obelisk-upgradedeleted

No changes to the content.

migration/9f6c8fd80ab3ff2dcc48bfd211c8b761-24f151a674662ac47845237d7a9f6a4f/obelisk-handoffdeleted

No changes to the content.

migration/9f6c8fd80ab3ff2dcc48bfd211c8b761-24f151a674662ac47845237d7a9f6a4f/obelisk-upgradedeleted

No changes to the content.

migration/9f71fc9d2ed805fecbc8c4a3aae5685b-cb2197d0267b52385ee39bf4adcfe475/obelisk-handoffdeleted

No changes to the content.

migration/9f71fc9d2ed805fecbc8c4a3aae5685b-cb2197d0267b52385ee39bf4adcfe475/obelisk-upgradedeleted

No changes to the content.

migration/9fb504b182cbd8dfb666175b651e0b49-338b92f88c49c7c3a1d8ec231d9f7189/obelisk-handoffdeleted

No changes to the content.

migration/9fb504b182cbd8dfb666175b651e0b49-338b92f88c49c7c3a1d8ec231d9f7189/obelisk-upgradedeleted

No changes to the content.

migration/9fe2922674fcee5e840c9f52cf534f88-ac92878eb85c5283bcc5f3f34cd35aa6/obelisk-handoffdeleted

No changes to the content.

migration/9fe2922674fcee5e840c9f52cf534f88-ac92878eb85c5283bcc5f3f34cd35aa6/obelisk-upgradedeleted

No changes to the content.

migration/a00870cfca85ff8dae5db3a78f5a227d-c2502429502b46133a749f021d188cf0/obelisk-handoffdeleted

No changes to the content.

migration/a00870cfca85ff8dae5db3a78f5a227d-c2502429502b46133a749f021d188cf0/obelisk-upgradedeleted

No changes to the content.

migration/a080b8549e32f21fec11084f4ab1e052-bd7c24fc03bf82e5a990515531a69147/obelisk-handoffdeleted

No changes to the content.

migration/a080b8549e32f21fec11084f4ab1e052-bd7c24fc03bf82e5a990515531a69147/obelisk-upgradedeleted

No changes to the content.

migration/a08aebb6f40fb45d174b229532314ac4-1c1c3f2b4426d5d26377eef08dbccb3c/obelisk-handoffdeleted

No changes to the content.

migration/a08aebb6f40fb45d174b229532314ac4-1c1c3f2b4426d5d26377eef08dbccb3c/obelisk-upgradedeleted

No changes to the content.

migration/a0b418c582d5c6ef9e0cd9a19abe046e-63422a9bade5c703461b600e03e227f0/obelisk-handoffdeleted

No changes to the content.

migration/a0b418c582d5c6ef9e0cd9a19abe046e-63422a9bade5c703461b600e03e227f0/obelisk-upgradedeleted

No changes to the content.

migration/a0f5ac2ff5c6a957e565f898a9fc8bfb-bf3fbc8db3d6fd42a37982dfb5a30b5c/obelisk-handoffdeleted

No changes to the content.

migration/a0f5ac2ff5c6a957e565f898a9fc8bfb-bf3fbc8db3d6fd42a37982dfb5a30b5c/obelisk-upgradedeleted

No changes to the content.

migration/a262678b7d849de5a9732d06199666ae-ebb95c5864d1d1d2f78fbf7cc8235dac/obelisk-handoffdeleted

No changes to the content.

migration/a262678b7d849de5a9732d06199666ae-ebb95c5864d1d1d2f78fbf7cc8235dac/obelisk-upgradedeleted

No changes to the content.

migration/a2665cfd303a4a34a7bfa3b7d0d264bb-aff3f143ddaa4ed3ffad873d6638e277/obelisk-handoffdeleted

No changes to the content.

migration/a2665cfd303a4a34a7bfa3b7d0d264bb-aff3f143ddaa4ed3ffad873d6638e277/obelisk-upgradedeleted

No changes to the content.

migration/a2b33f8dc1f90b8dddce942b174be5b8-00a480749e96ef7e21fbe8f4943152b1/obelisk-handoffdeleted

No changes to the content.

migration/a2b33f8dc1f90b8dddce942b174be5b8-00a480749e96ef7e21fbe8f4943152b1/obelisk-upgradedeleted

No changes to the content.

migration/a2bdbf1985b75442e78e84523c4ced5f-48c027da19d895ba03e7b9120f771291/obelisk-handoffdeleted

No changes to the content.

migration/a2bdbf1985b75442e78e84523c4ced5f-48c027da19d895ba03e7b9120f771291/obelisk-upgradedeleted

No changes to the content.

migration/a34d758508a0a5d08bc10e76482c4d11-8fca2c4d6478ed8628925852b2af45bf/obelisk-handoffdeleted

No changes to the content.

migration/a34d758508a0a5d08bc10e76482c4d11-8fca2c4d6478ed8628925852b2af45bf/obelisk-upgradedeleted

No changes to the content.

migration/a4477746ae1a7024bc3cd0807180d418-fb5110e527cf77c568bcae7d435ee035/obelisk-handoffdeleted

No changes to the content.

migration/a4477746ae1a7024bc3cd0807180d418-fb5110e527cf77c568bcae7d435ee035/obelisk-upgradedeleted

No changes to the content.

migration/a5297d8457aa4c327f35d4697f6cb66f-2f928f9c4a137186f808a231c9e3c141/obelisk-handoffdeleted

No changes to the content.

migration/a5297d8457aa4c327f35d4697f6cb66f-2f928f9c4a137186f808a231c9e3c141/obelisk-upgradedeleted

No changes to the content.

migration/a536d01b9c489f5e00e30abf27427da0-b8a8ffc909e5fc350d9f8a6a90e7b3d9/obelisk-handoffdeleted

No changes to the content.

migration/a536d01b9c489f5e00e30abf27427da0-b8a8ffc909e5fc350d9f8a6a90e7b3d9/obelisk-upgradedeleted

No changes to the content.

migration/a5904e3a899e346fe2fe26314f69c075-e4513b485b6ba8b7f4bd34b12458ff0c/obelisk-handoffdeleted

No changes to the content.

migration/a5904e3a899e346fe2fe26314f69c075-e4513b485b6ba8b7f4bd34b12458ff0c/obelisk-upgradedeleted

No changes to the content.

migration/a5bbc0b9ceab9c3f7ca32a7da8c1f685-3c4b3152f0608d23d07240e2df3ff8da/obelisk-handoffdeleted

No changes to the content.

migration/a5bbc0b9ceab9c3f7ca32a7da8c1f685-3c4b3152f0608d23d07240e2df3ff8da/obelisk-upgradedeleted

No changes to the content.

migration/a5e40d67714a1b2daa06e3fb3293216d-04a5ae41e8fcef5f719cb84d01430382/obelisk-handoffdeleted

No changes to the content.

migration/a5e40d67714a1b2daa06e3fb3293216d-04a5ae41e8fcef5f719cb84d01430382/obelisk-upgradedeleted

No changes to the content.

migration/a632452cf9c5956becd1a40512472b43-d6a3ea5f7eb3c95f5e5ea09fb1330270/obelisk-handoffdeleted

No changes to the content.

migration/a632452cf9c5956becd1a40512472b43-d6a3ea5f7eb3c95f5e5ea09fb1330270/obelisk-upgradedeleted

No changes to the content.

migration/a6633fc9687566aa84c3d5d5e2bff4fd-b5cab336f6244293913433101cd0feff/obelisk-handoffdeleted

No changes to the content.

migration/a6633fc9687566aa84c3d5d5e2bff4fd-b5cab336f6244293913433101cd0feff/obelisk-upgradedeleted

No changes to the content.

migration/a6d50c7e756dfd1e3ae891111b81a1ae-270b3b3f7bc510c692e3253a895f122a/obelisk-handoffdeleted

No changes to the content.

migration/a6d50c7e756dfd1e3ae891111b81a1ae-270b3b3f7bc510c692e3253a895f122a/obelisk-upgradedeleted

No changes to the content.

migration/a7c7b80a875d1863f027c97eb433b884-cbe155a3e856fb7eaad0bd18feb408be/obelisk-handoffdeleted

No changes to the content.

migration/a7c7b80a875d1863f027c97eb433b884-cbe155a3e856fb7eaad0bd18feb408be/obelisk-upgradedeleted

No changes to the content.

migration/a808a04519bbf2874e41028ded9c180c-8e1e426afe242ea3079f160bccd94cf0/obelisk-handoffdeleted

No changes to the content.

migration/a808a04519bbf2874e41028ded9c180c-8e1e426afe242ea3079f160bccd94cf0/obelisk-upgradedeleted

No changes to the content.

migration/a825fbb74e8d050a5ce10ec4c62b83f5-c3298d81428117be58196b80853e3c7b/obelisk-handoffdeleted

No changes to the content.

migration/a825fbb74e8d050a5ce10ec4c62b83f5-c3298d81428117be58196b80853e3c7b/obelisk-upgradedeleted

No changes to the content.

migration/a826f01311fe00ef5cf87568832605d3-9fb504b182cbd8dfb666175b651e0b49/obelisk-handoffdeleted

No changes to the content.

migration/a826f01311fe00ef5cf87568832605d3-9fb504b182cbd8dfb666175b651e0b49/obelisk-upgradedeleted

No changes to the content.

migration/a878ad346b9305ad2ae3fd85410a7ed7-0f78d9d6875933336bbf0c1c8888b73f/obelisk-handoffdeleted

No changes to the content.

migration/a878ad346b9305ad2ae3fd85410a7ed7-0f78d9d6875933336bbf0c1c8888b73f/obelisk-upgradedeleted

No changes to the content.

migration/a8ebe201ab7a7b0211de41b14a95192e-65071e255719af6389857646bc087d37/obelisk-handoffdeleted

No changes to the content.

migration/a8ebe201ab7a7b0211de41b14a95192e-65071e255719af6389857646bc087d37/obelisk-upgradedeleted

No changes to the content.

migration/a96d491705acc139315264fd340d228f-be615dc4d60c9d8710fbd120857c2fa1/obelisk-handoffdeleted

No changes to the content.

migration/a96d491705acc139315264fd340d228f-be615dc4d60c9d8710fbd120857c2fa1/obelisk-upgradedeleted

No changes to the content.

migration/aa8e6712fd46824acdea303068634078-dc95f39fb27634ccbca7318ff2065608/obelisk-handoffdeleted

No changes to the content.

migration/aa8e6712fd46824acdea303068634078-dc95f39fb27634ccbca7318ff2065608/obelisk-upgradedeleted

No changes to the content.

migration/aaf979dd0121603a8b48127e1ea47c7d-4b7a36dce2c1cf94554e12d8a7903269/obelisk-handoffdeleted

No changes to the content.

migration/aaf979dd0121603a8b48127e1ea47c7d-4b7a36dce2c1cf94554e12d8a7903269/obelisk-upgradedeleted

No changes to the content.

migration/abdb013baf130e1bd7e7fd04e535c44c-39df6544d6b035d4c77cc829729f9c19/obelisk-handoffdeleted

No changes to the content.

migration/abdb013baf130e1bd7e7fd04e535c44c-39df6544d6b035d4c77cc829729f9c19/obelisk-upgradedeleted

No changes to the content.

migration/ac5e28caddd6832d0bf1f7445a4a6a28-e01cc01684deaf4c75ad5d65270bf3d5/obelisk-handoffdeleted

No changes to the content.

migration/ac5e28caddd6832d0bf1f7445a4a6a28-e01cc01684deaf4c75ad5d65270bf3d5/obelisk-upgradedeleted

No changes to the content.

migration/ac92878eb85c5283bcc5f3f34cd35aa6-145650b66e83adf5bdb8261593076379/obelisk-handoffdeleted

No changes to the content.

migration/ac92878eb85c5283bcc5f3f34cd35aa6-145650b66e83adf5bdb8261593076379/obelisk-upgradedeleted

No changes to the content.

migration/adad34a1075a2fd51e1a1c06ec9d002d-930e9213e072e87c207f93461bd3c96c/obelisk-handoffdeleted

No changes to the content.

migration/adad34a1075a2fd51e1a1c06ec9d002d-930e9213e072e87c207f93461bd3c96c/obelisk-upgradedeleted

No changes to the content.

migration/ae4328daeb7d2c52c534a687fbd184b6-3e763ebe16cc3a261f5776ab1f1f6d9a/obelisk-handoffdeleted

No changes to the content.

migration/ae4328daeb7d2c52c534a687fbd184b6-3e763ebe16cc3a261f5776ab1f1f6d9a/obelisk-upgradedeleted

No changes to the content.

migration/ae824b83b569b3b23b8e198190371ca0-81f3dbeef0d78bfb523cd2ac113b8ad8/obelisk-handoffdeleted

No changes to the content.

migration/ae824b83b569b3b23b8e198190371ca0-81f3dbeef0d78bfb523cd2ac113b8ad8/obelisk-upgradedeleted

No changes to the content.

migration/ae8862f8b7292690c852111ca5659ce9-ae4328daeb7d2c52c534a687fbd184b6/obelisk-handoffdeleted

No changes to the content.

migration/ae8862f8b7292690c852111ca5659ce9-ae4328daeb7d2c52c534a687fbd184b6/obelisk-upgradedeleted

No changes to the content.

migration/aeafaa5d20bab4439a5d31f29023a839-79357758056556793db0472562d62ff9/obelisk-handoffdeleted

No changes to the content.

migration/aeafaa5d20bab4439a5d31f29023a839-79357758056556793db0472562d62ff9/obelisk-upgradedeleted

No changes to the content.

migration/aef6ac052515a869c7d3061d4ccc69dd-15f3f3f517b38664131b85da0594522f/obelisk-handoffdeleted

No changes to the content.

migration/aef6ac052515a869c7d3061d4ccc69dd-15f3f3f517b38664131b85da0594522f/obelisk-upgradedeleted

No changes to the content.

migration/afbd010add526d309dccee6895e42d0d-6f3a0f756847ee5aa208a1dc79404779/obelisk-handoffdeleted

No changes to the content.

migration/afbd010add526d309dccee6895e42d0d-6f3a0f756847ee5aa208a1dc79404779/obelisk-upgradedeleted

No changes to the content.

migration/aff3f143ddaa4ed3ffad873d6638e277-6fc44f180bb825d5b372444339681135/obelisk-handoffdeleted

No changes to the content.

migration/aff3f143ddaa4ed3ffad873d6638e277-6fc44f180bb825d5b372444339681135/obelisk-upgradedeleted

No changes to the content.

migration/b0c1d039514a686529561f186332c91e-a878ad346b9305ad2ae3fd85410a7ed7/obelisk-handoffdeleted

No changes to the content.

migration/b0c1d039514a686529561f186332c91e-a878ad346b9305ad2ae3fd85410a7ed7/obelisk-upgradedeleted

No changes to the content.

migration/b1692927da5ed59af72fac5be8094fa2-d5ba6f13296205e5bd88ca2756107df0/obelisk-handoffdeleted

No changes to the content.

migration/b1692927da5ed59af72fac5be8094fa2-d5ba6f13296205e5bd88ca2756107df0/obelisk-upgradedeleted

No changes to the content.

migration/b2282679f6f63cec89d921c3dd512d1e-1f09fcf196bfe90ed52f113c38ffbfdb/obelisk-handoffdeleted

No changes to the content.

migration/b2282679f6f63cec89d921c3dd512d1e-1f09fcf196bfe90ed52f113c38ffbfdb/obelisk-upgradedeleted

No changes to the content.

migration/b3b107d406a6a7675cd5b9f52e5f7d8e-0d3b4cfe9b4c72b830780a4c44942176/obelisk-handoffdeleted

No changes to the content.

migration/b3b107d406a6a7675cd5b9f52e5f7d8e-0d3b4cfe9b4c72b830780a4c44942176/obelisk-upgradedeleted

No changes to the content.

migration/b3ff195b0a90ef5900f9cef2ba75f2fe-24e0d027f4160a6478c1356e51f419cf/obelisk-handoffdeleted

No changes to the content.

migration/b3ff195b0a90ef5900f9cef2ba75f2fe-24e0d027f4160a6478c1356e51f419cf/obelisk-upgradedeleted

No changes to the content.

migration/b4f6596f2c16811442d8bb2961fbb721-3c7755e495f20cfac5e4dabe78644c5a/obelisk-handoffdeleted

No changes to the content.

migration/b4f6596f2c16811442d8bb2961fbb721-3c7755e495f20cfac5e4dabe78644c5a/obelisk-upgradedeleted

No changes to the content.

migration/b5cab336f6244293913433101cd0feff-8746b8f08b75b2bb7c22d8f17f22d72d/obelisk-handoffdeleted

No changes to the content.

migration/b5cab336f6244293913433101cd0feff-8746b8f08b75b2bb7c22d8f17f22d72d/obelisk-upgradedeleted

No changes to the content.

migration/b641dd9c5359201200e483a784456cc2-cc4307fc88e3486b1c3f9ce07db95808/obelisk-handoffdeleted

No changes to the content.

migration/b641dd9c5359201200e483a784456cc2-cc4307fc88e3486b1c3f9ce07db95808/obelisk-upgradedeleted

No changes to the content.

migration/b6425c7661af2116b912a558e9548013-4aefa80392582ec03a19f946e752d331/obelisk-handoffdeleted

No changes to the content.

migration/b6425c7661af2116b912a558e9548013-4aefa80392582ec03a19f946e752d331/obelisk-upgradedeleted

No changes to the content.

migration/b68a785fbf27a63334ef1d1fe82b2dac-d6b34eba6ed4f89875a6615fe2c41ec2/obelisk-handoffdeleted

No changes to the content.

migration/b68a785fbf27a63334ef1d1fe82b2dac-d6b34eba6ed4f89875a6615fe2c41ec2/obelisk-upgradedeleted

No changes to the content.

migration/b6ffce85249f30616da76334c7bbec6e-66a9b6b9e9bc4a5b0cfa78127479b231/obelisk-handoffdeleted

No changes to the content.

migration/b6ffce85249f30616da76334c7bbec6e-66a9b6b9e9bc4a5b0cfa78127479b231/obelisk-upgradedeleted

No changes to the content.

migration/b7630e5f30252987bf899c74de3953ad-3f0687592a77c0c66193652e9cd981fd/obelisk-handoffdeleted

No changes to the content.

migration/b7630e5f30252987bf899c74de3953ad-3f0687592a77c0c66193652e9cd981fd/obelisk-upgradedeleted

No changes to the content.

migration/b7e30314f9d0e69efdc84c7eb4e3bcc3-e4051219d89b3c9a8350fe824ad96a82/obelisk-handoffdeleted

No changes to the content.

migration/b7e30314f9d0e69efdc84c7eb4e3bcc3-e4051219d89b3c9a8350fe824ad96a82/obelisk-upgradedeleted

No changes to the content.

migration/b82e9c3fe22feff4bb3a6be49943fd6c-fbb3eb3209281031dc5183d289eb4e75/obelisk-handoffdeleted

No changes to the content.

migration/b82e9c3fe22feff4bb3a6be49943fd6c-fbb3eb3209281031dc5183d289eb4e75/obelisk-upgradedeleted

No changes to the content.

migration/b8a8ffc909e5fc350d9f8a6a90e7b3d9-aef6ac052515a869c7d3061d4ccc69dd/obelisk-handoffdeleted

No changes to the content.

migration/b8a8ffc909e5fc350d9f8a6a90e7b3d9-aef6ac052515a869c7d3061d4ccc69dd/obelisk-upgradedeleted

No changes to the content.

migration/b8aa42d4f2e5ef7db9ba34ecf6c42079-37c2ed0ba2cd54c0ec29eae9043bc4c8/obelisk-handoffdeleted

No changes to the content.

migration/b8aa42d4f2e5ef7db9ba34ecf6c42079-37c2ed0ba2cd54c0ec29eae9043bc4c8/obelisk-upgradedeleted

No changes to the content.

migration/b95c8d049256fb7360ca14b12acd8544-e7b17aa124e17c357cead8081a58f089/obelisk-handoffdeleted

No changes to the content.

migration/b95c8d049256fb7360ca14b12acd8544-e7b17aa124e17c357cead8081a58f089/obelisk-upgradedeleted

No changes to the content.

migration/ba0ccd5ef6110f6c5133d9d6ea6277f0-e78ec85bec14f4784faeef4d8d3e4d0a/obelisk-handoffdeleted

No changes to the content.

migration/ba0ccd5ef6110f6c5133d9d6ea6277f0-e78ec85bec14f4784faeef4d8d3e4d0a/obelisk-upgradedeleted

No changes to the content.

migration/ba4829dc61dcc12bcafd8de5fc8a76dc-2164344be67da5d0e57052c15f30646d/obelisk-handoffdeleted

No changes to the content.

migration/ba4829dc61dcc12bcafd8de5fc8a76dc-2164344be67da5d0e57052c15f30646d/obelisk-upgradedeleted

No changes to the content.

migration/bab3871bcfbfcb3d2bad4495d44ae4f8-9cef2dd1885f16455ff9b6801ec99454/obelisk-handoffdeleted

No changes to the content.

migration/bab3871bcfbfcb3d2bad4495d44ae4f8-9cef2dd1885f16455ff9b6801ec99454/obelisk-upgradedeleted

No changes to the content.

migration/bb2f504aa4f6f1ab8ed75697dc773efb-bcd13da3a7fba8514084c1a9dbeabb31/obelisk-handoffdeleted

No changes to the content.

migration/bb2f504aa4f6f1ab8ed75697dc773efb-bcd13da3a7fba8514084c1a9dbeabb31/obelisk-upgradedeleted

No changes to the content.

migration/bb9a87fc325204313d89dbfb689dba2f-d2d86ce0ca16b143b49374302a9e9e2f/obelisk-handoffdeleted

No changes to the content.

migration/bb9a87fc325204313d89dbfb689dba2f-d2d86ce0ca16b143b49374302a9e9e2f/obelisk-upgradedeleted

No changes to the content.

migration/bc7688ff47218c0e13d02d917b03caa0-2bdd4221307cafa3e98c4d3017cdc912/obelisk-handoffdeleted

No changes to the content.

migration/bc7688ff47218c0e13d02d917b03caa0-2bdd4221307cafa3e98c4d3017cdc912/obelisk-upgradedeleted

No changes to the content.

migration/bcd13da3a7fba8514084c1a9dbeabb31-8dcf46725e839f4a5dcb7a9f0235e01b/obelisk-handoffdeleted

No changes to the content.

migration/bcd13da3a7fba8514084c1a9dbeabb31-8dcf46725e839f4a5dcb7a9f0235e01b/obelisk-upgradedeleted

No changes to the content.

migration/bd6728737dffcc3505d30ada344e2f4b-1cc8d98f6f61441570fdde2d697e6e3c/obelisk-handoffdeleted

No changes to the content.

migration/bd6728737dffcc3505d30ada344e2f4b-1cc8d98f6f61441570fdde2d697e6e3c/obelisk-upgradedeleted

No changes to the content.

migration/bd7c24fc03bf82e5a990515531a69147-a5e40d67714a1b2daa06e3fb3293216d/obelisk-handoffdeleted

No changes to the content.

migration/bd7c24fc03bf82e5a990515531a69147-a5e40d67714a1b2daa06e3fb3293216d/obelisk-upgradedeleted

No changes to the content.

migration/bd8f5fa095c3eb19b87d85247b36f1df-5d90abd974b4ec53091a8cb3fc2258ce/obelisk-handoffdeleted

No changes to the content.

migration/bd8f5fa095c3eb19b87d85247b36f1df-5d90abd974b4ec53091a8cb3fc2258ce/obelisk-upgradedeleted

No changes to the content.

migration/be5a7b436cef0ae35cb074700d5f0d00-7c0ab1210424b615251ef57036d316c3/obelisk-handoffdeleted

No changes to the content.

migration/be5a7b436cef0ae35cb074700d5f0d00-7c0ab1210424b615251ef57036d316c3/obelisk-upgradedeleted

No changes to the content.

migration/be615dc4d60c9d8710fbd120857c2fa1-1e376437f069662df26178e4a7873190/obelisk-handoffdeleted

No changes to the content.

migration/be615dc4d60c9d8710fbd120857c2fa1-1e376437f069662df26178e4a7873190/obelisk-upgradedeleted

No changes to the content.

migration/beaa30317dadbef830531237279cbca4-a0f5ac2ff5c6a957e565f898a9fc8bfb/obelisk-handoffdeleted

No changes to the content.

migration/beaa30317dadbef830531237279cbca4-a0f5ac2ff5c6a957e565f898a9fc8bfb/obelisk-upgradedeleted

No changes to the content.

migration/bf22c91488f01ebf503c1786ee805d0c-64ee9e40f4943e0122398e48019f5616/obelisk-handoffdeleted

No changes to the content.

migration/bf22c91488f01ebf503c1786ee805d0c-64ee9e40f4943e0122398e48019f5616/obelisk-upgradedeleted

No changes to the content.

migration/bf3fbc8db3d6fd42a37982dfb5a30b5c-0c6127453069ad707e34ddfedb67e2f9/obelisk-handoffdeleted

No changes to the content.

migration/bf3fbc8db3d6fd42a37982dfb5a30b5c-0c6127453069ad707e34ddfedb67e2f9/obelisk-upgradedeleted

No changes to the content.

migration/bf81caf2a7d528f86acacc1f5d2ffcfa-a5bbc0b9ceab9c3f7ca32a7da8c1f685/obelisk-handoffdeleted

No changes to the content.

migration/bf81caf2a7d528f86acacc1f5d2ffcfa-a5bbc0b9ceab9c3f7ca32a7da8c1f685/obelisk-upgradedeleted

No changes to the content.

migration/c0811b664036c7996fcfd19fee241a10-a2bdbf1985b75442e78e84523c4ced5f/obelisk-handoffdeleted

No changes to the content.

migration/c0811b664036c7996fcfd19fee241a10-a2bdbf1985b75442e78e84523c4ced5f/obelisk-upgradedeleted

No changes to the content.

migration/c0c1452986b8d4c181bb0c6c83c56a2f-bf22c91488f01ebf503c1786ee805d0c/obelisk-handoffdeleted

No changes to the content.

migration/c0c1452986b8d4c181bb0c6c83c56a2f-bf22c91488f01ebf503c1786ee805d0c/obelisk-upgradedeleted

No changes to the content.

migration/c106f9398d5c203a1a296b85c7411bf9-1c2c93819c07f33ad899e39ec6769c3c/obelisk-handoffdeleted

No changes to the content.

migration/c106f9398d5c203a1a296b85c7411bf9-1c2c93819c07f33ad899e39ec6769c3c/obelisk-upgradedeleted

No changes to the content.

migration/c118e1ee09ec8d9aa3edf04e211ae035-749df832c9305e1c6309a794b0c1b201/obelisk-handoffdeleted

No changes to the content.

migration/c118e1ee09ec8d9aa3edf04e211ae035-749df832c9305e1c6309a794b0c1b201/obelisk-upgradedeleted

No changes to the content.

migration/c203adc5fed9bf79f67df43d72a4631a-e6980e47ba847245dfab61fb4d5b8ce5/obelisk-handoffdeleted

No changes to the content.

migration/c203adc5fed9bf79f67df43d72a4631a-e6980e47ba847245dfab61fb4d5b8ce5/obelisk-upgradedeleted

No changes to the content.

migration/c2502429502b46133a749f021d188cf0-93f164bbd887f688fd49bb80afffccd6/obelisk-handoffdeleted

No changes to the content.

migration/c2502429502b46133a749f021d188cf0-93f164bbd887f688fd49bb80afffccd6/obelisk-upgradedeleted

No changes to the content.

migration/c2d4eeb20f2b319a2d9dc5b6b0762f83-01c0ee6c93a3f9f69e097c41169927ac/obelisk-handoffdeleted

No changes to the content.

migration/c2d4eeb20f2b319a2d9dc5b6b0762f83-01c0ee6c93a3f9f69e097c41169927ac/obelisk-upgradedeleted

No changes to the content.

migration/c2d581d48dd821a94d7906ca8f33b504-81223455459db28cdbc990c3d4e6eedd/obelisk-handoffdeleted

No changes to the content.

migration/c2d581d48dd821a94d7906ca8f33b504-81223455459db28cdbc990c3d4e6eedd/obelisk-upgradedeleted

No changes to the content.

migration/c3298d81428117be58196b80853e3c7b-f68462dc88ed7fc451d1c757c040cfee/obelisk-handoffdeleted

No changes to the content.

migration/c3298d81428117be58196b80853e3c7b-f68462dc88ed7fc451d1c757c040cfee/obelisk-upgradedeleted

No changes to the content.

migration/c3a8b70b1290031feb21880914cb081e-c2d581d48dd821a94d7906ca8f33b504/obelisk-handoffdeleted

No changes to the content.

migration/c3a8b70b1290031feb21880914cb081e-c2d581d48dd821a94d7906ca8f33b504/obelisk-upgradedeleted

No changes to the content.

migration/c56de2f95c90def16704f62639246bbc-77a721a8fa97d1f84dd2af622089871b/obelisk-handoffdeleted

No changes to the content.

migration/c56de2f95c90def16704f62639246bbc-77a721a8fa97d1f84dd2af622089871b/obelisk-upgradedeleted

No changes to the content.

migration/c5a306045ad998f906f80748fe18487f-e474bad466cd6e413fba753746eb58ef/obelisk-handoffdeleted

No changes to the content.

migration/c5a306045ad998f906f80748fe18487f-e474bad466cd6e413fba753746eb58ef/obelisk-upgradedeleted

No changes to the content.

migration/c66bc5ba27dec52d144ceb0aa429e449-1b1686b03b41bd840ff46776cec7b33a/obelisk-handoffdeleted

No changes to the content.

migration/c66bc5ba27dec52d144ceb0aa429e449-1b1686b03b41bd840ff46776cec7b33a/obelisk-upgradedeleted

No changes to the content.

migration/c67f206b6d2626eb6d36b00122b52784-3bc53f3a28cb8f403cc41c11a37eec5d/obelisk-handoffdeleted

No changes to the content.

migration/c67f206b6d2626eb6d36b00122b52784-3bc53f3a28cb8f403cc41c11a37eec5d/obelisk-upgradedeleted

No changes to the content.

migration/c6baede3962454713b73acefcc92fe75-a96d491705acc139315264fd340d228f/obelisk-handoffdeleted

No changes to the content.

migration/c6baede3962454713b73acefcc92fe75-a96d491705acc139315264fd340d228f/obelisk-upgradedeleted

No changes to the content.

migration/c6f38304ee53762590c67020f30d2ab4-58391ba73c13d922e8a12116b28caad2/obelisk-handoffdeleted

No changes to the content.

migration/c6f38304ee53762590c67020f30d2ab4-58391ba73c13d922e8a12116b28caad2/obelisk-upgradedeleted

No changes to the content.

migration/c7dbf17de8aebd3043617ea6db790dcc-d35f6eb2d1b066d366c9a89ab26144e1/obelisk-handoffdeleted

No changes to the content.

migration/c7dbf17de8aebd3043617ea6db790dcc-d35f6eb2d1b066d366c9a89ab26144e1/obelisk-upgradedeleted

No changes to the content.

migration/c801cbcf925c0a7b084ef152e3c08f8e-8d5dba70e93ae7e784ea238281842408/obelisk-handoffdeleted

No changes to the content.

migration/c801cbcf925c0a7b084ef152e3c08f8e-8d5dba70e93ae7e784ea238281842408/obelisk-upgradedeleted

No changes to the content.

migration/c93529e579e64263fd91663216fd3fe7-12c8a24c34c2d8441f3181d179c2baab/obelisk-handoffdeleted

No changes to the content.

migration/c93529e579e64263fd91663216fd3fe7-12c8a24c34c2d8441f3181d179c2baab/obelisk-upgradedeleted

No changes to the content.

migration/ca5ee8f372ecb07b09283081f0298733-36cdb1c744c3036c2932f01d5fadd9f5/obelisk-handoffdeleted

No changes to the content.

migration/ca5ee8f372ecb07b09283081f0298733-36cdb1c744c3036c2932f01d5fadd9f5/obelisk-upgradedeleted

No changes to the content.

migration/ca88f98cf9fb63d2feaef7fc19b30eca-962e2bd1ae8d0c233f8da89ecebffe50/obelisk-handoffdeleted

No changes to the content.

migration/ca88f98cf9fb63d2feaef7fc19b30eca-962e2bd1ae8d0c233f8da89ecebffe50/obelisk-upgradedeleted

No changes to the content.

migration/cb2197d0267b52385ee39bf4adcfe475-fbe0c6b69a63f3a44109a2c7526bc577/obelisk-handoffdeleted

No changes to the content.

migration/cb2197d0267b52385ee39bf4adcfe475-fbe0c6b69a63f3a44109a2c7526bc577/obelisk-upgradedeleted

No changes to the content.

migration/cb2d040cc16752b1dc89eecf200ea798-5734398c41e4eb5bb58d933ed2e12041/obelisk-handoffdeleted

No changes to the content.

migration/cb2d040cc16752b1dc89eecf200ea798-5734398c41e4eb5bb58d933ed2e12041/obelisk-upgradedeleted

No changes to the content.

migration/cb98f93880e6dea74014976977d6eb93-a7c7b80a875d1863f027c97eb433b884/obelisk-handoffdeleted

No changes to the content.

migration/cb98f93880e6dea74014976977d6eb93-a7c7b80a875d1863f027c97eb433b884/obelisk-upgradedeleted

No changes to the content.

migration/cba28ee4b736cf8ed43cb342ccaef12e-8e01c7fee83aa74229a77772d8c928f3/obelisk-handoffdeleted

No changes to the content.

migration/cba28ee4b736cf8ed43cb342ccaef12e-8e01c7fee83aa74229a77772d8c928f3/obelisk-upgradedeleted

No changes to the content.

migration/cbe155a3e856fb7eaad0bd18feb408be-4c9372bf9aa6ebed1ea20abf3dbb0075/obelisk-handoffdeleted

No changes to the content.

migration/cbe155a3e856fb7eaad0bd18feb408be-4c9372bf9aa6ebed1ea20abf3dbb0075/obelisk-upgradedeleted

No changes to the content.

migration/cc4307fc88e3486b1c3f9ce07db95808-0fba399e046852d4ecf8e1dad831a2f9/obelisk-handoffdeleted

No changes to the content.

migration/cc4307fc88e3486b1c3f9ce07db95808-0fba399e046852d4ecf8e1dad831a2f9/obelisk-upgradedeleted

No changes to the content.

migration/ccadc106cd03c9c32e77457d3adc77a1-aa8e6712fd46824acdea303068634078/obelisk-handoffdeleted

No changes to the content.

migration/ccadc106cd03c9c32e77457d3adc77a1-aa8e6712fd46824acdea303068634078/obelisk-upgradedeleted

No changes to the content.

migration/cccf4140dc336263489bcf67c8c58f86-d50a30fd9643486c5848399d5ba7f1f1/obelisk-handoffdeleted

No changes to the content.

migration/cccf4140dc336263489bcf67c8c58f86-d50a30fd9643486c5848399d5ba7f1f1/obelisk-upgradedeleted

No changes to the content.

migration/cd38e239c47fb92fabd867b7f86fc99a-7447d29f4712c72458c049cdb9380753/obelisk-handoffdeleted

No changes to the content.

migration/cd38e239c47fb92fabd867b7f86fc99a-7447d29f4712c72458c049cdb9380753/obelisk-upgradedeleted

No changes to the content.

migration/cd5b54d21dd0fb868f48624d41021f47-0fe60df438daa8a2cca5f2b569e361a5/obelisk-handoffdeleted

No changes to the content.

migration/cd5b54d21dd0fb868f48624d41021f47-0fe60df438daa8a2cca5f2b569e361a5/obelisk-upgradedeleted

No changes to the content.

migration/cd86427cc2d5b2e931d7ab03deb19d20-bd8f5fa095c3eb19b87d85247b36f1df/obelisk-handoffdeleted

No changes to the content.

migration/cd86427cc2d5b2e931d7ab03deb19d20-bd8f5fa095c3eb19b87d85247b36f1df/obelisk-upgradedeleted

No changes to the content.

migration/ce7f0d15d71d36d4bcf4fbadcdfa1b58-4db97ca86399c557e5ee0d04d0838acc/obelisk-handoffdeleted

No changes to the content.

migration/ce7f0d15d71d36d4bcf4fbadcdfa1b58-4db97ca86399c557e5ee0d04d0838acc/obelisk-upgradedeleted

No changes to the content.

migration/cffcc6cab6080a35b7bf7c5c7facd6bc-5631293b75143d170b8fca5579e15fee/obelisk-handoffdeleted

No changes to the content.

migration/cffcc6cab6080a35b7bf7c5c7facd6bc-5631293b75143d170b8fca5579e15fee/obelisk-upgradedeleted

No changes to the content.

migration/d125109aa22f485d282f6ceb7cd5936e-9b9d73736773550dde7d36de2498b029/obelisk-handoffdeleted

No changes to the content.

migration/d125109aa22f485d282f6ceb7cd5936e-9b9d73736773550dde7d36de2498b029/obelisk-upgradedeleted

No changes to the content.

migration/d16784e9a2d93db7f953941e3003fa8c-a262678b7d849de5a9732d06199666ae/obelisk-handoffdeleted

No changes to the content.

migration/d16784e9a2d93db7f953941e3003fa8c-a262678b7d849de5a9732d06199666ae/obelisk-upgradedeleted

No changes to the content.

migration/d17b41a4669ac17960559614c830d69a-66f7a274206ad9f6d3c5701e4bcffc9d/obelisk-handoffdeleted

No changes to the content.

migration/d17b41a4669ac17960559614c830d69a-66f7a274206ad9f6d3c5701e4bcffc9d/obelisk-upgradedeleted

No changes to the content.

migration/d1b3f5f5375558bf91d55c88547784b9-c0811b664036c7996fcfd19fee241a10/obelisk-handoffdeleted

No changes to the content.

migration/d1b3f5f5375558bf91d55c88547784b9-c0811b664036c7996fcfd19fee241a10/obelisk-upgradedeleted

No changes to the content.

migration/d207ab99328b8ece5fb27fa1f4dbf9f6-e6a85005bd7e5d343bc23e17bfc345f6/obelisk-handoffdeleted

No changes to the content.

migration/d207ab99328b8ece5fb27fa1f4dbf9f6-e6a85005bd7e5d343bc23e17bfc345f6/obelisk-upgradedeleted

No changes to the content.

migration/d2d86ce0ca16b143b49374302a9e9e2f-84f4d5c5491abb855d8dbe3acf3bc8e6/obelisk-handoffdeleted

No changes to the content.

migration/d2d86ce0ca16b143b49374302a9e9e2f-84f4d5c5491abb855d8dbe3acf3bc8e6/obelisk-upgradedeleted

No changes to the content.

migration/d327ceb69d24e40dc11792840454cff9-de469e6488ddf192f2104ec0a030daf7/obelisk-handoffdeleted

No changes to the content.

migration/d327ceb69d24e40dc11792840454cff9-de469e6488ddf192f2104ec0a030daf7/obelisk-upgradedeleted

No changes to the content.

migration/d35f6eb2d1b066d366c9a89ab26144e1-03b9747848d7f3e995b118f776ef96c7/obelisk-handoffdeleted

No changes to the content.

migration/d35f6eb2d1b066d366c9a89ab26144e1-03b9747848d7f3e995b118f776ef96c7/obelisk-upgradedeleted

No changes to the content.

migration/d37d70fe5d7d11e68b59ce12022281c9-5f0d3f7480d67df7deebdb880726b356/obelisk-handoffdeleted

No changes to the content.

migration/d37d70fe5d7d11e68b59ce12022281c9-5f0d3f7480d67df7deebdb880726b356/obelisk-upgradedeleted

No changes to the content.

migration/d40e0e3051a76c4c198cc11c2e8e4b7c-1859cc62bb2f89f528bb2aa170c36e84/obelisk-handoffdeleted

No changes to the content.

migration/d40e0e3051a76c4c198cc11c2e8e4b7c-1859cc62bb2f89f528bb2aa170c36e84/obelisk-upgradedeleted

No changes to the content.

migration/d50a30fd9643486c5848399d5ba7f1f1-1f5b6e2a19c87d3820b953f942ab4a27/obelisk-handoffdeleted

No changes to the content.

migration/d50a30fd9643486c5848399d5ba7f1f1-1f5b6e2a19c87d3820b953f942ab4a27/obelisk-upgradedeleted

No changes to the content.

migration/d532356dfb65a4c8a39452d2ebca2516-bf81caf2a7d528f86acacc1f5d2ffcfa/obelisk-handoffdeleted

No changes to the content.

migration/d532356dfb65a4c8a39452d2ebca2516-bf81caf2a7d528f86acacc1f5d2ffcfa/obelisk-upgradedeleted

No changes to the content.

migration/d5ba6f13296205e5bd88ca2756107df0-26a2491fc8a9658751c1511c777a67bd/obelisk-handoffdeleted

No changes to the content.

migration/d5ba6f13296205e5bd88ca2756107df0-26a2491fc8a9658751c1511c777a67bd/obelisk-upgradedeleted

No changes to the content.

migration/d62d6d443d9c2261b7e09664398aa005-fcc617e973168e1210379a6afe7171be/obelisk-handoffdeleted

No changes to the content.

migration/d62d6d443d9c2261b7e09664398aa005-fcc617e973168e1210379a6afe7171be/obelisk-upgradedeleted

No changes to the content.

migration/d6a3ea5f7eb3c95f5e5ea09fb1330270-bc7688ff47218c0e13d02d917b03caa0/obelisk-handoffdeleted

No changes to the content.

migration/d6a3ea5f7eb3c95f5e5ea09fb1330270-bc7688ff47218c0e13d02d917b03caa0/obelisk-upgradedeleted

No changes to the content.

migration/d6b34eba6ed4f89875a6615fe2c41ec2-3daecb03985f83d8af466a9e0541782b/obelisk-handoffdeleted

No changes to the content.

migration/d6b34eba6ed4f89875a6615fe2c41ec2-3daecb03985f83d8af466a9e0541782b/obelisk-upgradedeleted

No changes to the content.

migration/d6c090699acd25fc60213ff4bd9eebcf-0298e61f6a03e553df08dcef03e27bd1/obelisk-handoffdeleted

No changes to the content.

migration/d6c090699acd25fc60213ff4bd9eebcf-0298e61f6a03e553df08dcef03e27bd1/obelisk-upgradedeleted

No changes to the content.

migration/d6cacd7f4fab95c4b6aa2d6c1a4ad2a0-4d6197d8166de78f882376102e36300b/obelisk-handoffdeleted

No changes to the content.

migration/d6cacd7f4fab95c4b6aa2d6c1a4ad2a0-4d6197d8166de78f882376102e36300b/obelisk-upgradedeleted

No changes to the content.

migration/d747c3d0620b2034aca44265dd1c8c86-eeb970434a53f4883c9f0255db555581/obelisk-handoffdeleted

No changes to the content.

migration/d747c3d0620b2034aca44265dd1c8c86-eeb970434a53f4883c9f0255db555581/obelisk-upgradedeleted

No changes to the content.

migration/d8adb57b2f2266e128ebb6f6d7e65920-ccadc106cd03c9c32e77457d3adc77a1/obelisk-handoffdeleted

No changes to the content.

migration/d8adb57b2f2266e128ebb6f6d7e65920-ccadc106cd03c9c32e77457d3adc77a1/obelisk-upgradedeleted

No changes to the content.

migration/da23605ee4058c8474687e74a3999354-dfd7dbceea141c60bbfca962d50c1c06/obelisk-handoffdeleted

No changes to the content.

migration/da23605ee4058c8474687e74a3999354-dfd7dbceea141c60bbfca962d50c1c06/obelisk-upgradedeleted

No changes to the content.

migration/db327f87e60d4e59bdbfd482e5abe985-eb99c1a66237ba99cd1f6d244cafb9cc/obelisk-handoffdeleted

No changes to the content.

migration/db327f87e60d4e59bdbfd482e5abe985-eb99c1a66237ba99cd1f6d244cafb9cc/obelisk-upgradedeleted

No changes to the content.

migration/dc95f39fb27634ccbca7318ff2065608-0fb3a912a8aaf5ef6f734a25f0bce7e2/obelisk-handoffdeleted

No changes to the content.

migration/dc95f39fb27634ccbca7318ff2065608-0fb3a912a8aaf5ef6f734a25f0bce7e2/obelisk-upgradedeleted

No changes to the content.

migration/dd6ca52a58ea6cda7b3eaa2e87bb13d9-a6d50c7e756dfd1e3ae891111b81a1ae/obelisk-handoffdeleted

No changes to the content.

migration/dd6ca52a58ea6cda7b3eaa2e87bb13d9-a6d50c7e756dfd1e3ae891111b81a1ae/obelisk-upgradedeleted

No changes to the content.

migration/ddaa8e0e42a1ef00154d580a390c9f17-53d29061537ddaf7530aef88624ab266/obelisk-handoffdeleted

No changes to the content.

migration/ddaa8e0e42a1ef00154d580a390c9f17-53d29061537ddaf7530aef88624ab266/obelisk-upgradedeleted

No changes to the content.

migration/de469e6488ddf192f2104ec0a030daf7-3da7d4a3b507d18097a90becf4dde9aa/obelisk-handoffdeleted

No changes to the content.

migration/de469e6488ddf192f2104ec0a030daf7-3da7d4a3b507d18097a90becf4dde9aa/obelisk-upgradedeleted

No changes to the content.

migration/df71cd82e801ca701bf4bc44b80b6d50-f423ecd4d096de9dca37a67b8b4143eb/obelisk-handoffdeleted

No changes to the content.

migration/df71cd82e801ca701bf4bc44b80b6d50-f423ecd4d096de9dca37a67b8b4143eb/obelisk-upgradedeleted

No changes to the content.

migration/dfd7dbceea141c60bbfca962d50c1c06-030ed18dacf3571cbb9e3e7d9d99627b/obelisk-handoffdeleted

No changes to the content.

migration/dfd7dbceea141c60bbfca962d50c1c06-030ed18dacf3571cbb9e3e7d9d99627b/obelisk-upgradedeleted

No changes to the content.

migration/e01cc01684deaf4c75ad5d65270bf3d5-c7dbf17de8aebd3043617ea6db790dcc/obelisk-handoffdeleted

No changes to the content.

migration/e01cc01684deaf4c75ad5d65270bf3d5-c7dbf17de8aebd3043617ea6db790dcc/obelisk-upgradedeleted

No changes to the content.

migration/e1ef432db54d9a6daa9bdeeb543262ae-2ba356b187db8db13b8aa40f43ca025f/obelisk-handoffdeleted

No changes to the content.

migration/e1ef432db54d9a6daa9bdeeb543262ae-2ba356b187db8db13b8aa40f43ca025f/obelisk-upgradedeleted

No changes to the content.

migration/e22c51145e08d4a089003460ae80a7f9-42357310c09e345f9360259c6c4102f2/obelisk-handoffdeleted

No changes to the content.

migration/e22c51145e08d4a089003460ae80a7f9-42357310c09e345f9360259c6c4102f2/obelisk-upgradedeleted

No changes to the content.

migration/e24a771ee59f24e45afc0a27307f2070-2eb907820012f77e60350430d89631fe/obelisk-handoffdeleted

No changes to the content.

migration/e24a771ee59f24e45afc0a27307f2070-2eb907820012f77e60350430d89631fe/obelisk-upgradedeleted

No changes to the content.

migration/e2706f337218a12a3bdddd18fa3f4cfd-475f9a0d63e84e9cf13b8be3986cb603/obelisk-handoffdeleted

No changes to the content.

migration/e2706f337218a12a3bdddd18fa3f4cfd-475f9a0d63e84e9cf13b8be3986cb603/obelisk-upgradedeleted

No changes to the content.

migration/e2b2d878b0ba6eb653b194523bf0a0e0-5f307639ccf6a64309166bfe58037397/obelisk-handoffdeleted

No changes to the content.

migration/e2b2d878b0ba6eb653b194523bf0a0e0-5f307639ccf6a64309166bfe58037397/obelisk-upgradedeleted

No changes to the content.

migration/e2be519915794ef130249f2be7640382-455279b6974b96a673613cd0ccc8ed54/obelisk-handoffdeleted

No changes to the content.

migration/e2be519915794ef130249f2be7640382-455279b6974b96a673613cd0ccc8ed54/obelisk-upgradedeleted

No changes to the content.

migration/e323ce1352e512109f1609276fb509b4-2c1521e5a5be64e6d28a9fdae950cd76/obelisk-handoffdeleted

No changes to the content.

migration/e323ce1352e512109f1609276fb509b4-2c1521e5a5be64e6d28a9fdae950cd76/obelisk-upgradedeleted

No changes to the content.

migration/e327773e1525eaa1eb2112dff8f41b26-f595b01939b4c87b7c146ea9ff331b39/obelisk-handoffdeleted

No changes to the content.

migration/e327773e1525eaa1eb2112dff8f41b26-f595b01939b4c87b7c146ea9ff331b39/obelisk-upgradedeleted

No changes to the content.

migration/e3b3014d7e0dc983fe118d32546654e5-063665b78b6705e7de952f73fa9797bf/obelisk-handoffdeleted

No changes to the content.

migration/e3b3014d7e0dc983fe118d32546654e5-063665b78b6705e7de952f73fa9797bf/obelisk-upgradedeleted

No changes to the content.

migration/e4051219d89b3c9a8350fe824ad96a82-166c5f8e4a0f1dfd227b075a96237386/obelisk-handoffdeleted

No changes to the content.

migration/e4051219d89b3c9a8350fe824ad96a82-166c5f8e4a0f1dfd227b075a96237386/obelisk-upgradedeleted

No changes to the content.

migration/e4513b485b6ba8b7f4bd34b12458ff0c-3c80eccfe80d0abd36b07bb11e69fc7a/obelisk-handoffdeleted

No changes to the content.

migration/e4513b485b6ba8b7f4bd34b12458ff0c-3c80eccfe80d0abd36b07bb11e69fc7a/obelisk-upgradedeleted

No changes to the content.

migration/e474bad466cd6e413fba753746eb58ef-f19b3699616f3b1de3a277f9cba617c7/obelisk-handoffdeleted

No changes to the content.

migration/e474bad466cd6e413fba753746eb58ef-f19b3699616f3b1de3a277f9cba617c7/obelisk-upgradedeleted

No changes to the content.

migration/e4b89dd259f3831bc60b56147b3586a6-98da197f133bca81e533ab5ffbf4c82d/obelisk-handoffdeleted

No changes to the content.

migration/e4b89dd259f3831bc60b56147b3586a6-98da197f133bca81e533ab5ffbf4c82d/obelisk-upgradedeleted

No changes to the content.

migration/e609d945b642fd9d4ce99931e3699db7-85c2c4fb8472d50eaa97aed0bdc9b142/obelisk-handoffdeleted

No changes to the content.

migration/e609d945b642fd9d4ce99931e3699db7-85c2c4fb8472d50eaa97aed0bdc9b142/obelisk-upgradedeleted

No changes to the content.

migration/e6980e47ba847245dfab61fb4d5b8ce5-58f9d23d2cede2081dc6f6cfa2283f60/obelisk-handoffdeleted

No changes to the content.

migration/e6980e47ba847245dfab61fb4d5b8ce5-58f9d23d2cede2081dc6f6cfa2283f60/obelisk-upgradedeleted

No changes to the content.

migration/e6a85005bd7e5d343bc23e17bfc345f6-98f9e0bbd21e36b0f4daa440d11e7177/obelisk-handoffdeleted

No changes to the content.

migration/e6a85005bd7e5d343bc23e17bfc345f6-98f9e0bbd21e36b0f4daa440d11e7177/obelisk-upgradedeleted

No changes to the content.

migration/e7288bb723a4e5b7139ac67842963287-7b75ffdcd86b5a330d9c325ad5767393/obelisk-handoffdeleted

No changes to the content.

migration/e7288bb723a4e5b7139ac67842963287-7b75ffdcd86b5a330d9c325ad5767393/obelisk-upgradedeleted

No changes to the content.

migration/e7385c9eae2b807a483b73be4295b207-fb02559b6f723d599e12931a81be3a1b/obelisk-handoffdeleted

No changes to the content.

migration/e7385c9eae2b807a483b73be4295b207-fb02559b6f723d599e12931a81be3a1b/obelisk-upgradedeleted

No changes to the content.

migration/e7667778f2ab3055aa3ccc68443d14df-7adece557941a7d7ac7c150f77f758be/obelisk-handoffdeleted

No changes to the content.

migration/e7667778f2ab3055aa3ccc68443d14df-7adece557941a7d7ac7c150f77f758be/obelisk-upgradedeleted

No changes to the content.

migration/e78ec85bec14f4784faeef4d8d3e4d0a-e7667778f2ab3055aa3ccc68443d14df/obelisk-handoffdeleted

No changes to the content.

migration/e78ec85bec14f4784faeef4d8d3e4d0a-e7667778f2ab3055aa3ccc68443d14df/obelisk-upgradedeleted

No changes to the content.

migration/e7b17aa124e17c357cead8081a58f089-ac5e28caddd6832d0bf1f7445a4a6a28/obelisk-handoffdeleted

No changes to the content.

migration/e7b17aa124e17c357cead8081a58f089-ac5e28caddd6832d0bf1f7445a4a6a28/obelisk-upgradedeleted

No changes to the content.

migration/e7b566495114e160f1f4d722b7ed8174-7cf814f7404638f1c1be662a24d7cfea/obelisk-handoffdeleted

No changes to the content.

migration/e7b566495114e160f1f4d722b7ed8174-7cf814f7404638f1c1be662a24d7cfea/obelisk-upgradedeleted

No changes to the content.

migration/e92786eaf56c32fd6b452c4a841d6317-f24a7fb6e3261c4919ea2a2fee83ed6b/obelisk-handoffdeleted

No changes to the content.

migration/e92786eaf56c32fd6b452c4a841d6317-f24a7fb6e3261c4919ea2a2fee83ed6b/obelisk-upgradedeleted

No changes to the content.

migration/e9ec84bfe7099b713c0fda32308f5815-f0a064ca5f709233204168c136b99a7e/obelisk-handoffdeleted

No changes to the content.

migration/e9ec84bfe7099b713c0fda32308f5815-f0a064ca5f709233204168c136b99a7e/obelisk-upgradedeleted

No changes to the content.

migration/eb99c1a66237ba99cd1f6d244cafb9cc-da23605ee4058c8474687e74a3999354/obelisk-handoffdeleted

No changes to the content.

migration/eb99c1a66237ba99cd1f6d244cafb9cc-da23605ee4058c8474687e74a3999354/obelisk-upgradedeleted

No changes to the content.

migration/ebb95c5864d1d1d2f78fbf7cc8235dac-ba4829dc61dcc12bcafd8de5fc8a76dc/obelisk-handoffdeleted

No changes to the content.

migration/ebb95c5864d1d1d2f78fbf7cc8235dac-ba4829dc61dcc12bcafd8de5fc8a76dc/obelisk-upgradedeleted

No changes to the content.

migration/ec11d10a6131abca9222a8f8026fb5e4-f3d494ffee7091cd588bbdd2fd9bcdf9/obelisk-handoffdeleted

No changes to the content.

migration/ec11d10a6131abca9222a8f8026fb5e4-f3d494ffee7091cd588bbdd2fd9bcdf9/obelisk-upgradedeleted

No changes to the content.

migration/ec94619fa3f25db1ca84d179e47db1ca-23af4d9671962484de53f537a0626d83/obelisk-handoffdeleted

No changes to the content.

migration/ec94619fa3f25db1ca84d179e47db1ca-23af4d9671962484de53f537a0626d83/obelisk-upgradedeleted

No changes to the content.

migration/ed95a99cdde4adc90c6a8e2de82410eb-1c4fc23cbf5653613c6060668e065f37/obelisk-handoffdeleted

No changes to the content.

migration/ed95a99cdde4adc90c6a8e2de82410eb-1c4fc23cbf5653613c6060668e065f37/obelisk-upgradedeleted

No changes to the content.

migration/ee3dccc89ea6fdaaf9c1146b92861e90-f3eac05465851232f8174a7a347d1c4b/obelisk-handoffdeleted

No changes to the content.

migration/ee3dccc89ea6fdaaf9c1146b92861e90-f3eac05465851232f8174a7a347d1c4b/obelisk-upgradedeleted

No changes to the content.

migration/eeb970434a53f4883c9f0255db555581-a2b33f8dc1f90b8dddce942b174be5b8/obelisk-handoffdeleted

No changes to the content.

migration/eeb970434a53f4883c9f0255db555581-a2b33f8dc1f90b8dddce942b174be5b8/obelisk-upgradedeleted

No changes to the content.

migration/ef33c06500054e09c97eee9e9c55485a-367616bd36eda757f251a5e8e9096acc/obelisk-handoffdeleted

No changes to the content.

migration/ef33c06500054e09c97eee9e9c55485a-367616bd36eda757f251a5e8e9096acc/obelisk-upgradedeleted

No changes to the content.

migration/ef6aebc8cba2af6c4f930c981b06e8ac-d17b41a4669ac17960559614c830d69a/obelisk-handoffdeleted

No changes to the content.

migration/ef6aebc8cba2af6c4f930c981b06e8ac-d17b41a4669ac17960559614c830d69a/obelisk-upgradedeleted

No changes to the content.

migration/f0a064ca5f709233204168c136b99a7e-ef6aebc8cba2af6c4f930c981b06e8ac/obelisk-handoffdeleted

No changes to the content.

migration/f0a064ca5f709233204168c136b99a7e-ef6aebc8cba2af6c4f930c981b06e8ac/obelisk-upgradedeleted

No changes to the content.

migration/f15c9c2f894c8cc4fac07e2cdef47180-94252585266fa745a91733888e2f50b7/obelisk-handoffdeleted

No changes to the content.

migration/f15c9c2f894c8cc4fac07e2cdef47180-94252585266fa745a91733888e2f50b7/obelisk-upgradedeleted

No changes to the content.

migration/f19b3699616f3b1de3a277f9cba617c7-4d85ee8cdb0db20a5662b09567a2bbe7/obelisk-handoffdeleted

No changes to the content.

migration/f19b3699616f3b1de3a277f9cba617c7-4d85ee8cdb0db20a5662b09567a2bbe7/obelisk-upgradedeleted

No changes to the content.

migration/f24a7fb6e3261c4919ea2a2fee83ed6b-f913f01e90549c56a8b05f8100cfa21d/obelisk-handoffdeleted

No changes to the content.

migration/f24a7fb6e3261c4919ea2a2fee83ed6b-f913f01e90549c56a8b05f8100cfa21d/obelisk-upgradedeleted

No changes to the content.

migration/f2a9ba4a93c3a04acbee2211bca9e27a-ec94619fa3f25db1ca84d179e47db1ca/obelisk-handoffdeleted

No changes to the content.

migration/f2a9ba4a93c3a04acbee2211bca9e27a-ec94619fa3f25db1ca84d179e47db1ca/obelisk-upgradedeleted

No changes to the content.

migration/f391c6a74ee14217ce4565c278fceed6-e7288bb723a4e5b7139ac67842963287/obelisk-handoffdeleted

No changes to the content.

migration/f391c6a74ee14217ce4565c278fceed6-e7288bb723a4e5b7139ac67842963287/obelisk-upgradedeleted

No changes to the content.

migration/f3d494ffee7091cd588bbdd2fd9bcdf9-b68a785fbf27a63334ef1d1fe82b2dac/obelisk-handoffdeleted

No changes to the content.

migration/f3d494ffee7091cd588bbdd2fd9bcdf9-b68a785fbf27a63334ef1d1fe82b2dac/obelisk-upgradedeleted

No changes to the content.

migration/f3eac05465851232f8174a7a347d1c4b-1629d0d70e360df6b2280fac36d21e8a/obelisk-handoffdeleted

No changes to the content.

migration/f3eac05465851232f8174a7a347d1c4b-1629d0d70e360df6b2280fac36d21e8a/obelisk-upgradedeleted

No changes to the content.

migration/f3ec62d09836804e39fad9d814137845-ef33c06500054e09c97eee9e9c55485a/obelisk-handoffdeleted

No changes to the content.

migration/f3ec62d09836804e39fad9d814137845-ef33c06500054e09c97eee9e9c55485a/obelisk-upgradedeleted

No changes to the content.

migration/f41b690c74938e347cf042cee2d4fc72-7fc381538805adac19c54b73ea118601/obelisk-handoffdeleted

No changes to the content.

migration/f41b690c74938e347cf042cee2d4fc72-7fc381538805adac19c54b73ea118601/obelisk-upgradedeleted

No changes to the content.

migration/f423ecd4d096de9dca37a67b8b4143eb-469a31d20d69da72e6a3a3ea2bb02f9d/obelisk-handoffdeleted

No changes to the content.

migration/f423ecd4d096de9dca37a67b8b4143eb-469a31d20d69da72e6a3a3ea2bb02f9d/obelisk-upgradedeleted

No changes to the content.

migration/f595b01939b4c87b7c146ea9ff331b39-4da182e21be35feeee484dc85be7a075/obelisk-handoffdeleted

No changes to the content.

migration/f595b01939b4c87b7c146ea9ff331b39-4da182e21be35feeee484dc85be7a075/obelisk-upgradedeleted

No changes to the content.

migration/f68462dc88ed7fc451d1c757c040cfee-589eb89cdb09655c5edde4cd2a4deb80/obelisk-handoffdeleted

No changes to the content.

migration/f68462dc88ed7fc451d1c757c040cfee-589eb89cdb09655c5edde4cd2a4deb80/obelisk-upgradedeleted

No changes to the content.

migration/f790ac9400501511b4ee045e22bc5fe1-a826f01311fe00ef5cf87568832605d3/obelisk-handoffdeleted

No changes to the content.

migration/f790ac9400501511b4ee045e22bc5fe1-a826f01311fe00ef5cf87568832605d3/obelisk-upgradedeleted

No changes to the content.

migration/f8e51a9e8ac6598f7c073b09e4ba09b5-ff57aebc7325bd7e02b436fec79e799c/obelisk-handoffdeleted

No changes to the content.

migration/f8e51a9e8ac6598f7c073b09e4ba09b5-ff57aebc7325bd7e02b436fec79e799c/obelisk-upgradedeleted

No changes to the content.

migration/f8eee9431c8b7a767d96da0a8cfae95d-9e05cc6b81c6380ba0ac59bad1021cac/obelisk-handoffdeleted

No changes to the content.

migration/f8eee9431c8b7a767d96da0a8cfae95d-9e05cc6b81c6380ba0ac59bad1021cac/obelisk-upgradedeleted

No changes to the content.

migration/f913f01e90549c56a8b05f8100cfa21d-001efbbe866b66eebc6ee1ea0a906f2d/obelisk-handoffdeleted

No changes to the content.

migration/f913f01e90549c56a8b05f8100cfa21d-001efbbe866b66eebc6ee1ea0a906f2d/obelisk-upgradedeleted

No changes to the content.

migration/f9716ad47c84e0b7ac7d426e8ad059f3-ec11d10a6131abca9222a8f8026fb5e4/obelisk-handoffdeleted

No changes to the content.

migration/f9716ad47c84e0b7ac7d426e8ad059f3-ec11d10a6131abca9222a8f8026fb5e4/obelisk-upgradedeleted

No changes to the content.

migration/f98b82f5d142ce83733a3f18bdacb138-35acc1e0c4d9c02d60abd116f3df84ee/obelisk-handoffdeleted

No changes to the content.

migration/f98b82f5d142ce83733a3f18bdacb138-35acc1e0c4d9c02d60abd116f3df84ee/obelisk-upgradedeleted

No changes to the content.

migration/f9ad863c0bebc103f8b81afdf82e6293-c6f38304ee53762590c67020f30d2ab4/obelisk-handoffdeleted

No changes to the content.

migration/f9ad863c0bebc103f8b81afdf82e6293-c6f38304ee53762590c67020f30d2ab4/obelisk-upgradedeleted

No changes to the content.

migration/fa18f7522903a21b615dbee24c26bfbe-0535e06dd0d61f3e6970b491a17cf1f8/obelisk-handoffdeleted

No changes to the content.

migration/fa18f7522903a21b615dbee24c26bfbe-0535e06dd0d61f3e6970b491a17cf1f8/obelisk-upgradedeleted

No changes to the content.

migration/fb02559b6f723d599e12931a81be3a1b-71a47d4d6856c1956c0d1c9de9af231f/obelisk-handoffdeleted

No changes to the content.

migration/fb02559b6f723d599e12931a81be3a1b-71a47d4d6856c1956c0d1c9de9af231f/obelisk-upgradedeleted

No changes to the content.

migration/fb374b0721716a1e849e64daf6da73c4-f8e51a9e8ac6598f7c073b09e4ba09b5/obelisk-handoffdeleted

No changes to the content.

migration/fb374b0721716a1e849e64daf6da73c4-f8e51a9e8ac6598f7c073b09e4ba09b5/obelisk-upgradedeleted

No changes to the content.

migration/fb5110e527cf77c568bcae7d435ee035-53fdf819333b86dfb485bf16a8f28db2/obelisk-handoffdeleted

No changes to the content.

migration/fb5110e527cf77c568bcae7d435ee035-53fdf819333b86dfb485bf16a8f28db2/obelisk-upgradedeleted

No changes to the content.

migration/fb81153791e49cd00850e91ff7418018-9b33a49f0c978e27b752fa457aca97cc/obelisk-handoffdeleted

No changes to the content.

migration/fb81153791e49cd00850e91ff7418018-9b33a49f0c978e27b752fa457aca97cc/obelisk-upgradedeleted

No changes to the content.

migration/fbb3eb3209281031dc5183d289eb4e75-8c7718f94e2676c957fe844b7dc2637c/obelisk-handoffdeleted

No changes to the content.

migration/fbb3eb3209281031dc5183d289eb4e75-8c7718f94e2676c957fe844b7dc2637c/obelisk-upgradedeleted

No changes to the content.

migration/fbe0c6b69a63f3a44109a2c7526bc577-c3a8b70b1290031feb21880914cb081e/obelisk-handoffdeleted

No changes to the content.

migration/fbe0c6b69a63f3a44109a2c7526bc577-c3a8b70b1290031feb21880914cb081e/obelisk-upgradedeleted

No changes to the content.

migration/fc42c5fc24031fc6a8836a10cfb45143-d125109aa22f485d282f6ceb7cd5936e/obelisk-handoffdeleted

No changes to the content.

migration/fc42c5fc24031fc6a8836a10cfb45143-d125109aa22f485d282f6ceb7cd5936e/obelisk-upgradedeleted

No changes to the content.

migration/fca48edfdaa31b036176b97b04384682-2df092178e167b474dd08dfa46ec3a49/obelisk-handoffdeleted

No changes to the content.

migration/fca48edfdaa31b036176b97b04384682-2df092178e167b474dd08dfa46ec3a49/obelisk-upgradedeleted

No changes to the content.

migration/fcc617e973168e1210379a6afe7171be-ed95a99cdde4adc90c6a8e2de82410eb/obelisk-handoffdeleted

No changes to the content.

migration/fcc617e973168e1210379a6afe7171be-ed95a99cdde4adc90c6a8e2de82410eb/obelisk-upgradedeleted

No changes to the content.

migration/ff1105290f89350bc9deb6eb907e0a53-0c991bc3c06801f72df48486ee78e350/obelisk-handoffdeleted

No changes to the content.

migration/ff1105290f89350bc9deb6eb907e0a53-0c991bc3c06801f72df48486ee78e350/obelisk-upgradedeleted

No changes to the content.

migration/ff57aebc7325bd7e02b436fec79e799c-9f71fc9d2ed805fecbc8c4a3aae5685b/obelisk-handoffdeleted

No changes to the content.

migration/ff57aebc7325bd7e02b436fec79e799c-9f71fc9d2ed805fecbc8c4a3aae5685b/obelisk-upgradedeleted

No changes to the content.

migration/ffa62158ed6fdece012980e4ad25e6ac-959ae2925bcd2b5beff631a8e7aeaf00/obelisk-handoffdeleted

No changes to the content.

migration/ffa62158ed6fdece012980e4ad25e6ac-959ae2925bcd2b5beff631a8e7aeaf00/obelisk-upgradedeleted

No changes to the content.

skeleton/backend/src/Backend.hs+1−1
@@ -10,7 +10,7 @@ import Obelisk.Backend
10import Obelisk.Route
10import Obelisk.Route
11import Obelisk.Api.Pipeline
11import Obelisk.Api.Pipeline
12
12
13−backend :: Backend Void1 IndexOnlyRoute
13+backend :: Backend BackendRoute FrontendRoute
14backend = Backend
14backend = Backend
15 { _backend_run = \serve -> serve $ const $ return ()
15 { _backend_run = \serve -> serve $ const $ return ()
16 , _backend_routeEncoder = backendRouteEncoder
16 , _backend_routeEncoder = backendRouteEncoder
skeleton/common/src/Common/Api.hs+1−1
@@ -1,4 +1,4 @@
1module Common.Api where
1module Common.Api where
2
2
3commonStuff :: String
3commonStuff :: String
4−commonStuff = "here is a common string"
4+commonStuff = "Here is a string defined in code common to the frontend and backend."
skeleton/common/src/Common/Route.hs+40−12
@@ -3,22 +3,50 @@
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
4{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE LambdaCase #-}
5{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RankNTypes #-}
6−module Common.Route where
6+{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE KindSignatures #-}
8+{-# LANGUAGE EmptyCase #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE FlexibleInstances #-}
11+{-# LANGUAGE OverloadedStrings #-}
7
12
8−import Prelude hiding ((.))
13+module Common.Route where
9
14
15+{- -- You will probably want these imports for composing Encoders.
16+import Prelude hiding (id, (.))
10import Control.Category
17import Control.Category
11−import Control.Monad.Except
18+-}
19+
20+import Data.Text (Text)
12−import Data.Text
21+import Data.Functor.Identity
13import Data.Functor.Sum
22import Data.Functor.Sum
23+
14import Obelisk.Route
24import Obelisk.Route
25+import Obelisk.Route.TH
26+
27+data BackendRoute :: * -> * where
28+ -- | Used to handle unparseable routes.
29+ BackendRoute_Missing :: BackendRoute ()
30+ -- You can define any routes that will be handled specially by the backend here.
31+ -- i.e. These do not serve the frontend, but do something different, such as serving static files.
32+
33+data FrontendRoute :: * -> * where
34+ FrontendRoute_Main :: FrontendRoute ()
35+ -- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend.
15
36
16backendRouteEncoder
37backendRouteEncoder
17− :: forall check parse. (check ~ parse, MonadError Text parse)
38+ :: Encoder (Either Text) Identity (R (Sum BackendRoute (ObeliskRoute FrontendRoute))) PageName
18− => Encoder check parse (R (Sum Void1 (ObeliskRoute IndexOnlyRoute))) PageName
19−backendRouteEncoder = Encoder $ do
39+backendRouteEncoder = handleEncoder (const (InL BackendRoute_Missing :/ ())) $
20− let componentEncoder = shadowEncoder void1Encoder $ obeliskRouteComponentEncoder indexOnlyRouteComponentEncoder
21− restEncoder <- checkObeliskRouteRestEncoder indexOnlyRouteRestEncoder
22− checkEncoder $ pathComponentEncoder (componentEncoder . someSumEncoder) $ \case
40+ pathComponentEncoder $ \case
23− InL backendRoute -> case backendRoute of {}
41+ InL backendRoute -> case backendRoute of
24− InR obeliskRoute -> runValidEncoderFunc restEncoder obeliskRoute
42+ BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty
43+ InR obeliskRoute -> obeliskRouteSegment obeliskRoute $ \case
44+ -- The encoder given to PathEnd determines how to parse query parameters,
45+ -- in this example, we have none, so we insist on it.
46+ FrontendRoute_Main -> PathEnd $ unitEncoder mempty
47+
48+
49+concat <$> mapM deriveRouteComponent
50+ [ ''BackendRoute
51+ , ''FrontendRoute
52+ ]
skeleton/frontend/frontend.cabal+2−2
@@ -10,7 +10,7 @@ library
10 , obelisk-frontend
10 , obelisk-frontend
11 , obelisk-route
11 , obelisk-route
12 , reflex-dom
12 , reflex-dom
13− , static
13+ , obelisk-generated-static
14 , text
14 , text
15 exposed-modules:
15 exposed-modules:
16 Frontend
16 Frontend
@@ -24,7 +24,7 @@ executable frontend
24 , obelisk-frontend
24 , obelisk-frontend
25 , obelisk-route
25 , obelisk-route
26 , reflex-dom
26 , reflex-dom
27− , static
27+ , obelisk-generated-static
28 , frontend
28 , frontend
29 --TODO: Make these ghc-options optional
29 --TODO: Make these ghc-options optional
30 ghc-options: -threaded
30 ghc-options: -threaded
skeleton/frontend/src/Frontend.hs+4−3
@@ -9,14 +9,15 @@ import Obelisk.Route
9import Reflex.Dom.Core
9import Reflex.Dom.Core
10
10
11import Common.Api
11import Common.Api
12−import Static
12+import Common.Route
13+import Obelisk.Generated.Static
13
14
15+
14−frontend :: Frontend (R IndexOnlyRoute)
16+frontend :: Frontend (R FrontendRoute)
15frontend = Frontend
17frontend = Frontend
16 { _frontend_head = el "title" $ text "Obelisk Minimal Example"
18 { _frontend_head = el "title" $ text "Obelisk Minimal Example"
17 , _frontend_body = do
19 , _frontend_body = do
18 text "Welcome to Obelisk!"
20 text "Welcome to Obelisk!"
19 el "p" $ text $ T.pack commonStuff
21 el "p" $ text $ T.pack commonStuff
20 elAttr "img" ("src" =: static @"obelisk.jpg") blank
22 elAttr "img" ("src" =: static @"obelisk.jpg") blank
21− , _frontend_notFoundRoute = const $ IndexOnlyRoute :/ ()
22 }
23 }