Merge remote-tracking branch 'origin/master' into db
# Conflicts:
# default.nix
# dep/reflex-platform/github.json
# lib/frontend/src/Obelisk/Frontend.hs
# skeleton/backend/src/Backend.hs
commitee81335818f038daae7b0dde491b02fbf7345a8d
authorRyan Trinkle <ryan.trinkle@gmail.com>
dateFri 19 Oct 2018, 16:41 (8 years ago)
parent1391592a3e870f
1080 files changed+1814−1689
README.md+11
@@ -7,6 +7,8 @@ Obelisk provides an easy way to develop and deploy your [Reflex](https://github.
77- [Deploying](#deploying)
88 - [Locally](#locally)
99 - [EC2](#ec2)
10+ - [From macOS](#from-macos)
11+ - [Deploying an updated version](#deploying-an-updated-version)
1012- [Mobile](#mobile)
1113 - [iOS](#ios)
1214 - [Android](#android)
@@ -156,6 +158,15 @@ ob deploy push
156158
157159At this point you are done. Your app will be accessible at `${ROUTE}`.
158160
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+
159170### Deploying an updated version
160171
161172If 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 @@
11{ system ? builtins.currentSystem
22, profiling ? false
33, iosSdkVersion ? "10.2"
4+, __useLegacyCompilers ? false
45}:
56let
6− getReflexPlatform = sys: import ./dep/reflex-platform { inherit iosSdkVersion; system = sys; };
7− reflex-platform = getReflexPlatform system;
8− inherit (reflex-platform) hackGet nixpkgs;
9− pkgs = nixpkgs;
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';
7+ cleanSource = builtins.filterSource (name: _: let baseName = builtins.baseNameOf name; in !(
8+ builtins.match "^\\.ghc\\.environment.*" baseName != null ||
9+ baseName == "cabal.project.local"
10+ ));
1811
1912 commandRuntimeDeps = pkgs: with pkgs; [
2013 coreutils
@@ -24,39 +17,6 @@ let
2417 openssh
2518 ];
2619
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−
6020 beam-src = pkgs.fetchFromGitHub {
6121 owner = "obsidiansystems";
6222 repo = "beam";
@@ -71,97 +31,150 @@ let
7131 sha256 = "0257p0qd8xx900ngghkjbmjnvn7pjv05g0jm5kkrm4p6alrlhfyl";
7232 };
7333
74− fixUpstreamPkgs = self: super: {
75− algebraic-graphs = pkgs.haskell.lib.doJailbreak
76− (self.callCabal2nix "algebraic-graphs" (pkgs.fetchFromGitHub {
77− owner = "snowleopard";
78− repo = "alga";
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− };
34+ getReflexPlatform = getReflexPlatform' __useLegacyCompilers;
35+ getReflexPlatform' = __useLegacyCompilers: sys: import ./dep/reflex-platform {
36+ inherit iosSdkVersion __useLegacyCompilers;
37+ system = sys;
38+ enableLibraryProfiling = profiling;
12039
121− cleanSource = builtins.filterSource (name: _: let baseName = builtins.baseNameOf name; in !(
122− builtins.match "^\\.ghc\\.environment.*" baseName != null ||
123− baseName == "cabal.project.local"
124− ));
40+ nixpkgsOverlays = [
41+ (self: super: {
42+ obeliskExecutableConfig = import ./lib/executable-config { nixpkgs = pkgs; filterGitSource = cleanSource; };
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) {};
12560
126− executableConfig = import ./lib/executable-config { nixpkgs = pkgs; filterGitSource = cleanSource; };
127−
128− addLibs = self: super: {
129− obelisk-asset-manifest = self.callCabal2nix "obelisk-asset-manifest" (hackGet ./lib/asset + "/manifest") {};
130− obelisk-asset-serve-snap = self.callCabal2nix "obelisk-asset-serve-snap" (hackGet ./lib/asset + "/serve-snap") {};
131− obelisk-backend = self.callCabal2nix "obelisk-backend" (cleanSource ./lib/backend) {};
132− obelisk-cliapp = self.callCabal2nix "obelisk-cliapp" (cleanSource ./lib/cliapp) {};
133− obelisk-command = (self.callCabal2nix "obelisk-command" (cleanSource ./lib/command) {});
134− obelisk-executable-config = executableConfig.haskellPackage self;
135− obelisk-executable-config-inject = executableConfig.platforms.web.inject self;
136− obelisk-frontend = self.callCabal2nix "obelisk-frontend" (cleanSource ./lib/frontend) {};
137− obelisk-migration = self.callCabal2nix "obelisk-migration" (cleanSource ./lib/migration) {};
138− obelisk-run = self.callCabal2nix "obelisk-run" (cleanSource ./lib/run) {};
139− obelisk-route = self.callCabal2nix "obelisk-route" (cleanSource ./lib/route) {};
140− obelisk-selftest = self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {};
141− obelisk-snap = self.callCabal2nix "obelisk-snap" (cleanSource ./lib/snap) {};
142− obelisk-snap-extras = self.callCabal2nix "obelisk-snap-extras" (cleanSource ./lib/snap-extras) {};
61+ algebraic-graphs = pkgs.haskell.lib.doJailbreak
62+ (self.callCabal2nix "algebraic-graphs" (pkgs.fetchFromGitHub {
63+ owner = "snowleopard";
64+ repo = "alga";
65+ rev = "480a73137e9b38ad3f1bc2c628847953d2fb3e25";
66+ sha256 = "0dpwi5ffs88brl3lz51bwb004c6zm8ds8pkw1vzsg2a6aaiyhlzl";
67+ }) {});
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 {
102+ obelisk-executable-config = pkgs.obeliskExecutableConfig.haskellPackage 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) {};
109+ obelisk-command = self.callCabal2nix "obelisk-command" (cleanSource ./lib/command) {};
110+ obelisk-frontend = self.callCabal2nix "obelisk-frontend" (cleanSource ./lib/frontend) {};
111+ obelisk-run = self.callCabal2nix "obelisk-run" (cleanSource ./lib/run) {};
112+ obelisk-route = self.callCabal2nix "obelisk-route" (cleanSource ./lib/route) {};
113+ obelisk-selftest = self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {};
114+ obelisk-snap = self.callCabal2nix "obelisk-snap" (cleanSource ./lib/snap) {};
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+ ];
143157 };
144158
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+
145166 inherit (import ./lib/asset/assets.nix { inherit nixpkgs; }) mkAssets;
146167
147− defaultHaskellOverrides = composeExtensions fixUpstreamPkgs addLibs;
148−in
149−with pkgs.lib;
150−rec {
168+ haskellLib = pkgs.haskell.lib;
169+
170+in rec {
151171 inherit reflex-platform;
152172 inherit (reflex-platform) nixpkgs pinBuildInputs;
173+ inherit (nixpkgs) lib;
153174 pathGit = ./.; # Used in CI by the migration graph hash algorithm to correctly ignore files.
154175 path = reflex-platform.filterGit ./.;
155176 obelisk = ghcObelisk;
156− commandWithMigration = ghcObelisk.obelisk-command.overrideAttrs (drv: {
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− '';
177+ command = ghcObelisk.obelisk-command;
165178 shell = pinBuildInputs "obelisk-shell" ([command] ++ commandRuntimeDeps pkgs) [];
166179
167180 selftest = pkgs.writeScript "selftest" ''
@@ -170,7 +183,7 @@ rec {
170183
171184 PATH="${command}/bin:$PATH"
172185 export OBELISK_IMPL="${hackGet ./.}"
173− "${justStaticExecutables' ghcObelisk.obelisk-selftest}/bin/obelisk-selftest" "$@"
186+ "${ghcObelisk.obelisk-selftest}/bin/obelisk-selftest" "$@"
174187 '';
175188 #TODO: Why can't I build ./skeleton directly as a derivation? `nix-build -E ./.` doesn't work
176189 skeleton = pkgs.runCommand "skeleton" {
@@ -178,10 +191,9 @@ rec {
178191 } ''
179192 ln -s "$dir" "$out"
180193 '';
181− nullIfAbsent = p: if pathExists p then p else null;
182− haskellOverrides = addLibs;
194+ nullIfAbsent = p: if lib.pathExists p then p else null;
183195 #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" {
185197 inherit src;
186198 outputs = [ "out" "haskellManifest" "symlinked" ];
187199 buildInputs = [
@@ -196,7 +208,7 @@ rec {
196208 compressedJs = frontend: pkgs.runCommand "compressedJs" { buildInputs = [ pkgs.closurecompiler ]; } ''
197209 mkdir $out
198210 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
200212 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
201213 echo "//# sourceMappingURL=all.js.map" >> all.js
202214 '';
@@ -272,13 +284,13 @@ rec {
272284 pkgs.runCommand "serverExe" {} ''
273285 mkdir $out
274286 set -eux
275− ln -s "${justStaticExecutables backend}"/bin/* $out/
287+ ln -s "${haskellLib.justStaticExecutables backend}"/bin/* $out/
276288 ln -s "${mkAssets assets}" $out/static.assets
277289 cp -r ${config} $out/config
278290 ln -s ${mkAssets (compressedJs frontend)} $out/frontend.jsexe.assets
279291 '';
280292
281− server = { exe, hostName, adminEmail, routeHost, enableHttps }@args:
293+ server = { exe, hostName, adminEmail, routeHost, enableHttps, config }@args:
282294 let
283295 nixos = import (pkgs.path + /nixos);
284296 in nixos {
@@ -295,13 +307,12 @@ rec {
295307 # An Obelisk project is a reflex-platform project with a predefined layout and role for each component
296308 project = base: projectDefinition:
297309 let configPath = base + "/config";
298− static = base + "/static";
299− processedStatic = processAssets { src = static; };
300310 projectOut = sys: (getReflexPlatform sys).project (args@{ nixpkgs, ... }:
301311 let mkProject = { android ? null #TODO: Better error when missing
302312 , ios ? null #TODO: Better error when missing
303313 , packages ? {}
304314 , overrides ? _: _: {}
315+ , staticFiles ? base + /static
305316 , tools ? _: []
306317 , shellToolOverrides ? _: _: {}
307318 , withHoogle ? false # Setting this to `true` makes shell reloading far slower
@@ -309,21 +320,20 @@ rec {
309320 let frontendName = "frontend";
310321 backendName = "backend";
311322 commonName = "common";
312− staticName = "static";
313− staticPath = base + "/static";
323+ staticName = "obelisk-generated-static";
324+ processedStatic = processAssets { src = staticFiles; };
314325 # The packages whose names and roles are defined by this package
315− predefinedPackages = filterAttrs (_: x: x != null) {
326+ predefinedPackages = lib.filterAttrs (_: x: x != null) {
316327 ${frontendName} = nullIfAbsent (base + "/frontend");
317328 ${commonName} = nullIfAbsent (base + "/common");
318329 ${backendName} = nullIfAbsent (base + "/backend");
319330 };
320331 combinedPackages = predefinedPackages // packages;
321332 projectOverrides = self: super: {
322− ${staticName} = dontHaddock (self.callCabal2nix "static" processedStatic.haskellManifest {});
323− ${backendName} = addBuildDepend super.${backendName} self.obelisk-run;
333+ ${staticName} = haskellLib.dontHaddock (self.callCabal2nix staticName processedStatic.haskellManifest {});
334+ ${backendName} = haskellLib.addBuildDepend super.${backendName} self.obelisk-run;
324335 };
325− totalOverrides = composeExtensions (composeExtensions defaultHaskellOverrides projectOverrides) overrides;
326− inherit (nixpkgs) lib;
336+ totalOverrides = lib.composeExtensions projectOverrides overrides;
327337 inherit (lib.strings) hasPrefix;
328338 privateConfigDirs = ["config/backend"];
329339 injectableConfig = builtins.filterSource (path: _:
@@ -334,16 +344,16 @@ rec {
334344 overrides = totalOverrides;
335345 packages = combinedPackages;
336346 shells = {
337− ghcSavedSplices = (filter (x: hasAttr x combinedPackages) [
347+ ghcSavedSplices = (lib.filter (x: lib.hasAttr x combinedPackages) [
338348 commonName
339349 frontendName
340350 ]);
341− ghc = (filter (x: hasAttr x combinedPackages) [
351+ ghc = (lib.filter (x: lib.hasAttr x combinedPackages) [
342352 backendName
343353 commonName
344354 frontendName
345355 ]);
346− ghcjs = filter (x: hasAttr x combinedPackages) [
356+ ghcjs = lib.filter (x: lib.hasAttr x combinedPackages) [
347357 frontendName
348358 commonName
349359 ];
@@ -351,33 +361,32 @@ rec {
351361 android = {
352362 ${if android == null then null else frontendName} = {
353363 executableName = "frontend";
354− ${if builtins.pathExists staticPath then "assets" else null} =
355− executableConfig.platforms.android.inject injectableConfig processedStatic.symlinked;
364+ ${if builtins.pathExists staticFiles then "assets" else null} =
365+ nixpkgs.obeliskExecutableConfig.platforms.android.inject injectableConfig processedStatic.symlinked;
356366 } // android;
357367 };
358368 ios = {
359369 ${if ios == null then null else frontendName} = {
360370 executableName = "frontend";
361− ${if builtins.pathExists staticPath then "staticSrc" else null} =
362− executableConfig.platforms.ios.inject injectableConfig processedStatic.symlinked;
371+ ${if builtins.pathExists staticFiles then "staticSrc" else null} =
372+ nixpkgs.obeliskExecutableConfig.platforms.ios.inject injectableConfig processedStatic.symlinked;
363373 } // ios;
364374 };
375+ passthru = { inherit android ios packages overrides tools shellToolOverrides withHoogle staticFiles; };
365376 };
366377 in mkProject (projectDefinition args));
367− serverOn = sys: config: serverExe (projectOut sys).ghc.backend (projectOut system).ghcjs.frontend static 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;
378+ serverOn = sys: config: serverExe (projectOut sys).ghc.backend (projectOut system).ghcjs.frontend (projectOut sys).passthru.staticFiles config;
379+ linuxExe = serverOn "x86_64-linux";
372380 in projectOut system // {
373− inherit exe linuxExe;
374− server = args@{ hostName, adminEmail, routeHost, enableHttps, config }:
375− server (args // { exe = serverOn "x86_64-linux" config;});
381+ linuxExeConfigurable = linuxExe;
382+ linuxExe = linuxExe (base + "/config");
383+ exe = serverOn system (base + "/config") ;
384+ server = args@{ hostName, adminEmail, routeHost, enableHttps, config }: let
385+ injectableConfig = builtins.filterSource (path: _: !(lib.hasPrefix (toString config + "/backend") (toString path))) config;
386+ in server (args // { exe = linuxExe injectableConfig; });
376387 obelisk = import (base + "/.obelisk/impl") {};
377388 };
378389 haskellPackageSets = {
379− ghc = reflex-platform.ghc.override {
380− overrides = defaultHaskellOverrides;
381− };
390+ inherit (reflex-platform) ghc;
382391 };
383392}
lib/asset/manifest/src-bin/generate.hs+1
@@ -12,5 +12,6 @@ main = do
1212 writeStaticProject paths haskellTarget $ StaticConfig
1313 { _staticConfig_packageName = T.pack packageName
1414 , _staticConfig_moduleName = T.pack moduleName
15+ , _staticConfig_rootPath = root
1516 }
1617 copyAndSymlink paths root fileTarget
lib/asset/manifest/src/Obelisk/Asset/Promoted.hs+15−2
@@ -10,6 +10,7 @@ module Obelisk.Asset.Promoted
1010import Obelisk.Asset.Gather
1111
1212import Data.Foldable
13+import qualified Data.List as L
1314import Language.Haskell.TH (runQ, pprint)
1415import Language.Haskell.TH.Syntax hiding (lift)
1516import GHC.TypeLits
@@ -27,15 +28,27 @@ import qualified Data.Map as Map
2728data StaticConfig = StaticConfig
2829 { _staticConfig_packageName :: Text --TODO: Better type
2930 , _staticConfig_moduleName :: Text --TODO: Better type
31+ , _staticConfig_rootPath :: FilePath
3032 }
3133
3234writeStaticProject :: Map FilePath FilePath -> FilePath -> StaticConfig -> IO ()
3335writeStaticProject paths target cfg = do
34− createDirectoryIfMissing True $ target </> "src"
36+ createDirectoryIfMissing True target
3537 T.writeFile (target </> T.unpack (_staticConfig_packageName cfg) <.> "cabal") $ staticCabalFile cfg
3638 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
3743 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+ ]
3952
4053staticCabalFile :: StaticConfig -> Text
4154staticCabalFile cfg = T.unlines
lib/backend/obelisk-backend.cabal+1
@@ -7,6 +7,7 @@ library
77 hs-source-dirs: src
88 build-depends: base,
99 bytestring,
10+ categories,
1011 data-default,
1112 dependent-sum,
1213 dependent-sum-template,
lib/backend/src/Obelisk/Backend.hs+19−16
@@ -18,8 +18,11 @@ module Obelisk.Backend
1818 , runBackend
1919 ) where
2020
21+import Prelude hiding (id, (.))
22+import Control.Category
2123import Control.Monad
2224import Control.Monad.Except
25+import Control.Categorical.Bifunctor
2326import Data.ByteString (ByteString)
2427import qualified Data.ByteString.Char8 as BSC8
2528import Data.Default (Default (..))
@@ -40,7 +43,7 @@ import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog
4043import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
4144
4245data 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
4447 , _backend_run :: ((R backendRoute -> Snap ()) -> IO ()) -> IO ()
4548 }
4649
@@ -52,8 +55,8 @@ data GhcjsApp route = GhcjsApp
5255
5356-- | Serve a frontend, which must be the same frontend that Obelisk has built and placed in the default location
5457--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 ()
56−serveDefaultObeliskApp frontend = serveObeliskApp defaultStaticAssets frontendApp
58+serveDefaultObeliskApp :: MonadSnap m => ([Text] -> m ()) -> Frontend (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
59+serveDefaultObeliskApp serveStaticAsset frontend = serveObeliskApp serveStaticAsset frontendApp
5760 where frontendApp = GhcjsApp
5861 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
5962 , _ghcjsApp_value = frontend
@@ -89,25 +92,26 @@ runSnapWithCommandLineArgs a = do
8992 -- Start the web server
9093 httpServe httpConf a
9194
92−getPageName :: (MonadSnap m, MonadError Text parse) => m (parse PageName)
95+getPageName :: (MonadSnap m) => m PageName
9396getPageName = do
9497 p <- getsRequest rqPathInfo
9598 q <- getsRequest rqQueryString
96− return $ _validEncoder_decode pageNameValidEncoder
97− ( "/" <> T.unpack (decodeUtf8 p)
98− , "?" <> T.unpack (decodeUtf8 q)
99− )
99+ let pageNameEncoder' :: Encoder Identity Identity PageName (String, String)
100+ pageNameEncoder' = bimap
101+ (unpackTextEncoder . pathSegmentsTextEncoder . listToNonEmptyEncoder)
102+ (unpackTextEncoder . queryParametersTextEncoder . toListMapEncoder)
103+ return $ decode pageNameEncoder' (T.unpack (decodeUtf8 p), T.unpack (decodeUtf8 q))
100104
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)
102106getRouteWith e = do
103107 pageName <- getPageName
104− return $ pageName >>= _validEncoder_decode e
108+ return $ tryDecode e pageName
105109
106−serveObeliskApp :: MonadSnap m => StaticAssets -> GhcjsApp (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
107−serveObeliskApp staticAssets frontendApp = \case
110+serveObeliskApp :: MonadSnap m => ([Text] -> m ()) -> GhcjsApp (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
111+serveObeliskApp serveStaticAsset frontendApp = \case
108112 ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp frontendApp $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
109113 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
111115 ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp frontendApp $ GhcjsAppRoute_Resource :/ pathSegments
112116 ResourceRoute_JSaddleWarp :=> Identity _ -> do
113117 let msg = "Error: Obelisk.Backend received jsaddle request"
@@ -140,10 +144,9 @@ runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend
140144 Right validFullEncoder -> _backend_run backend $ \serveRoute -> do
141145 runSnapWithCommandLineArgs $ do
142146 getRouteWith validFullEncoder >>= \case
143− Left e -> writeText e
144− Right r -> case r of
147+ Identity r -> case r of
145148 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
147150
148151renderGhcjsFrontend :: route -> Frontend route -> IO ByteString
149152renderGhcjsFrontend route f = do
lib/cliapp/obelisk-cliapp.cabal+1
@@ -12,6 +12,7 @@ library
1212 , bytestring
1313 , exceptions
1414 , io-streams
15+ , lens
1516 , logging-effect
1617 , monad-loops
1718 , mtl
lib/cliapp/src/Obelisk/CliApp.hs+9−2
@@ -6,7 +6,8 @@
66module Obelisk.CliApp
77 (
88 -- .Types
9− Cli
9+ CliLog
10+ , CliThrow
1011 , CliT(..)
1112 , runCli
1213 , CliConfig
@@ -20,22 +21,28 @@ module Obelisk.CliApp
2021 , withSpinner'
2122
2223 -- .Logging
24+ , AsUnstructuredError (..)
2325 , newCliConfig
2426 , getLogLevel
2527 , putLog
2628 , failWith
29+ , errorToWarning
2730 , withExitFailMessage
2831
2932 -- Control.Monad.Log
3033 , Severity (..)
3134
3235 -- .Process
33− , ProcessFailed (..)
36+ , ProcessFailure (..)
37+ , AsProcessFailure (..)
3438 , readProcessAndLogStderr
39+ , readProcessAndLogOutput
40+ , readCreateProcessWithExitCode
3541 , callProcessAndLogOutput
3642 , createProcess_
3743 , callProcess
3844 , callCommand
45+ , reconstructCommand
3946 ) where
4047
4148import Control.Monad.Log (Severity (..))
lib/cliapp/src/Obelisk/CliApp/Demo.hs+5−2
@@ -13,7 +13,10 @@ import Control.Monad.Catch (MonadMask)
1313
1414import Obelisk.CliApp
1515
16−cliDemo :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m) => m ()
16+cliDemo
17+ :: ( MonadIO m, MonadMask m
18+ , CliLog m, HasCliConfig e m, CliThrow e m, AsProcessFailure e, AsUnstructuredError e)
19+ => m ()
1720cliDemo = withSpinner "CLI Demo" $ do
1821 putLog Notice "This demo will showcase the CLI library functionality"
1922 _ <- withSpinner' "Searching long for something" (Just $ \c -> "Discovered " <> T.pack (show c) <> " objects") $ do
@@ -40,7 +43,7 @@ cliDemo = withSpinner "CLI Demo" $ do
4043 withSpinner "Looking around" $ do
4144 delay
4245 output <- readProcessAndLogStderr Notice $ proc "ls" ["-l", "/"]
43− putLog Notice $ "Output was: " <> T.pack output
46+ putLog Notice $ "Output was: " <> output
4447 delay
4548 callProcessAndLogOutput (Notice, Error) $ proc "ls" ["-l", "/does-not-exist"]
4649 delay
lib/cliapp/src/Obelisk/CliApp/Logging.hs+66−29
@@ -4,15 +4,36 @@
44{-# LANGUAGE LambdaCase #-}
55{-# LANGUAGE MultiParamTypeClasses #-}
66{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE RankNTypes #-}
78{-# LANGUAGE ScopedTypeVariables #-}
89-- | Provides a logging handler that facilitates safe ouputting to terminal using MVar based locking.
910-- | 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
1130
1231import Control.Concurrent (ThreadId, forkIO, killThread, threadDelay)
1332import Control.Concurrent.MVar (modifyMVar_, newMVar)
33+import Control.Lens (Prism', review)
1434import Control.Monad (unless, void, when)
1535import Control.Monad.Catch (MonadCatch, MonadMask, bracket, catch, throwM)
36+import Control.Monad.Except (throwError)
1637import Control.Monad.IO.Class (liftIO)
1738import Control.Monad.Log (Severity (..), WithSeverity (..), logMessage, runLoggingT)
1839import Control.Monad.Loops (iterateUntil)
@@ -25,23 +46,30 @@ import qualified Data.Text.IO as T
2546import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
2647 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
2748 SGR (SetColor, SetConsoleIntensity), clearLine)
28−import System.Exit (ExitCode (..), exitWith)
49+import System.Exit (ExitCode (..))
2950import System.IO (BufferMode (NoBuffering), hFlush, hReady, hSetBuffering, stderr, stdin, stdout)
3051
3152import qualified Obelisk.CliApp.TerminalString as TS
3253import Obelisk.CliApp.Types
3354
34−newCliConfig :: Severity -> Bool -> Bool -> IO CliConfig
35−newCliConfig sev noColor noSpinner = do
55+newCliConfig
56+ :: Severity
57+ -> Bool
58+ -> Bool
59+ -> (e -> (Text, Int))
60+ -> IO (CliConfig e)
61+newCliConfig sev noColor noSpinner errorLogExitCode = do
3662 level <- newIORef sev
3763 lock <- newMVar False
3864 tipDisplayed <- newIORef False
3965 stack <- newIORef ([], [])
40− return $ CliConfig level noColor noSpinner lock tipDisplayed stack
66+ return $ CliConfig level noColor noSpinner lock tipDisplayed stack errorLogExitCode
4167
42−runCli :: MonadIO m => CliConfig -> CliT m a -> m a
68+runCli :: MonadIO m => CliConfig e -> CliT e m a -> m a
4369runCli c =
4470 flip runLoggingT (handleLog c)
71+ . flip runReaderT (_cliConfig_errorLogExitCode c)
72+ . unDieT
4573 . flip runReaderT c
4674 . unCliT
4775
@@ -59,18 +87,18 @@ getSeverity = \case
5987 Output_LogRaw (WithSeverity sev _) -> Just sev
6088 _ -> Nothing
6189
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
6891getLogLevel = getLogLevel' =<< getCliConfig
6992
70−getLogLevel' :: MonadIO m => CliConfig -> m Severity
93+getLogLevel' :: MonadIO m => CliConfig e -> m Severity
7194getLogLevel' = liftIO . readIORef . _cliConfig_logLevel
7295
73−handleLog :: MonadIO m => CliConfig -> Output -> m ()
96+setLogLevel :: (MonadIO m, HasCliConfig e m) => Severity -> m ()
97+setLogLevel sev = do
98+ l <- _cliConfig_logLevel <$> getCliConfig
99+ liftIO $ writeIORef l sev
100+
101+handleLog :: MonadIO m => CliConfig e -> Output -> m ()
74102handleLog conf output = do
75103 level <- getLogLevel' conf
76104 liftIO $ modifyMVar_ (_cliConfig_lock conf) $ \wasOverwriting -> do
@@ -107,26 +135,35 @@ handleLog' noColor output = do
107135 hFlush stdout
108136 return $ isOverwrite output
109137
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−
116138-- | Like `putLog` but without the implicit newline added.
117−putLogRaw :: Cli m => Severity -> Text -> m ()
139+putLogRaw :: CliLog m => Severity -> Text -> m ()
118140putLogRaw sev = logMessage . Output_LogRaw . WithSeverity sev
119141
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+
120152-- | Like `putLog Alert` but also abrupts the program.
121−failWith :: (MonadIO m, Cli m) => Text -> m a
122−failWith s = do
123− putLog Alert s
124− liftIO $ exitWith $ ExitFailure 2
153+failWith :: (CliThrow e m, AsUnstructuredError e) => Text -> m a
154+failWith = throwError . review asUnstructuredError
155+
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
125162
126163-- | Intercept ExitFailure exceptions and log the given alert before exiting.
127164--
128165-- 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
130167withExitFailMessage msg f = f `catch` \(e :: ExitCode) -> do
131168 case e of
132169 ExitFailure _ -> putLog Alert msg
@@ -134,7 +171,7 @@ withExitFailMessage msg f = f `catch` \(e :: ExitCode) -> do
134171 throwM e
135172
136173-- | 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 ()
138175writeLog withNewLine noColor (WithSeverity severity s)
139176 | noColor && severity <= Warning = liftIO $ putFn $ T.pack (show severity) <> ": " <> s
140177 | not noColor && severity <= Error = TS.putStrWithSGR errorColors h withNewLine s
@@ -152,7 +189,7 @@ writeLog withNewLine noColor (WithSeverity severity s)
152189--
153190-- Call this function in a thread, and kill it to turn off keystroke monitoring.
154191allowUserToMakeLoggingVerbose
155− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
192+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
156193 => String -- ^ The key to press in order to make logging verbose
157194 -> m ()
158195allowUserToMakeLoggingVerbose keyCode = bracket showTip (liftIO . killThread) $ \_ -> do
@@ -185,7 +222,7 @@ getChars = reverse <$> f mempty
185222 True -> f (x:xs)
186223 False -> return (x:xs)
187224
188−fork :: (HasCliConfig m, MonadIO m) => CliT IO () -> m ThreadId
225+fork :: (HasCliConfig e m, MonadIO m) => CliT e IO () -> m ThreadId
189226fork f = do
190227 c <- getCliConfig
191228 liftIO $ forkIO $ runCli c f
lib/cliapp/src/Obelisk/CliApp/Process.hs+84−32
@@ -9,25 +9,30 @@
99-- | An extension of `System.Process` that integrates with logging (`Obelisk.CLI.Logging`)
1010-- and is thus spinner friendly.
1111module Obelisk.CliApp.Process
12− ( ProcessFailed (..)
12+ ( ProcessFailure (..)
13+ , AsProcessFailure (..)
1314 , readProcessAndLogStderr
15+ , readProcessAndLogOutput
16+ , readCreateProcessWithExitCode
1417 , callProcessAndLogOutput
1518 , createProcess
1619 , createProcess_
1720 , callProcess
1821 , callCommand
22+ , reconstructCommand
1923 ) where
2024
21−import Control.Applicative (liftA2)
22−import Control.Monad (void, (<=<))
23−import Control.Monad.Catch (Exception, MonadMask, throwM)
25+import Control.Monad ((<=<), join, void)
26+import Control.Monad.Except (throwError)
2427import Control.Monad.IO.Class (MonadIO, liftIO)
28+import Control.Lens (Prism', review)
29+import qualified Data.ByteString as BS
2530import qualified Data.ByteString.Char8 as BSC
2631import Data.Function (fix)
2732import Data.Monoid ((<>))
33+import Data.Text (Text)
2834import qualified Data.Text as T
29−import Data.Text.Encoding (decodeUtf8)
30−import qualified Data.Text.IO as T
35+import qualified Data.Text.Encoding as T
3136import System.Exit (ExitCode (..))
3237import System.IO (Handle)
3338import System.IO.Streams (InputStream, handleToInputStream)
@@ -39,58 +44,97 @@ import qualified System.Process as Process
3944
4045import Control.Monad.Log (Severity (..))
4146import Obelisk.CliApp.Logging (putLog, putLogRaw)
42−import Obelisk.CliApp.Types (Cli)
47+import Obelisk.CliApp.Types (CliLog, CliThrow)
4348
44−data ProcessFailed = ProcessFailed Process.CmdSpec Int -- exit code
49+-- TODO put back in `Obelisk.CliApp.Process` and use prisms for extensible exceptions
50+data ProcessFailure = ProcessFailure Process.CmdSpec Int -- exit code
4551 deriving Show
4652
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
4860
4961readProcessAndLogStderr
50− :: (MonadIO m, MonadMask m, Cli m)
51− => Severity -> CreateProcess -> m String
62+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
63+ => Severity -> CreateProcess -> m Text
5264readProcessAndLogStderr sev process = do
5365 (out, _err) <- withProcess process $ \_out err -> do
5466 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 }
5789
58−-- | Like `System.Process.callProcess` but logs the combined output--stdout and stderr with the corresponding
59−-- severity.
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+
99+-- | Like `System.Process.callProcess` but logs the combined output (stdout and stderr)
100+-- with the corresponding severity.
60101--
61−-- Usually this function is called as `callProcessAndLogOutput (Debug, Error)`. However some processes
62−-- are known to spit out diagnostic or informative messages in stderr, in which case it is advisable to call
63−-- it with a non-Error severity for stderr, like `callProcessAndLogOutput (Debug, Debug)`.
102+-- Usually this function is called as `callProcessAndLogOutput (Debug, Error)`. However
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
105+-- `callProcessAndLogOutput (Debug, Debug)`.
64106callProcessAndLogOutput
65− :: (MonadIO m, MonadMask m, Cli m)
107+
108+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
66109 => (Severity, Severity) -> CreateProcess -> m ()
67−callProcessAndLogOutput (sev_out, sev_err) process = do
110+callProcessAndLogOutput (sev_out, sev_err) process =
68111 void $ withProcess process $ \out err -> do
69− stream <- liftIO $ do
70− uncurry combineStream =<< liftA2 (,) (streamHandle sev_out out) (streamHandle sev_err err)
112+ stream <- liftIO $ join $ combineStream
113+ <$> streamHandle sev_out out
114+ <*> streamHandle sev_err err
71115 streamToLog stream
72116 where
73117 combineStream s1 s2 = concurrentMerge [s1, s2]
74118
75119-- | Like `System.Process.createProcess` but also logs (debug) the process being run
76120createProcess
77− :: (MonadIO m, Cli m)
121+ :: (MonadIO m, CliLog m)
78122 => CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
79123createProcess p = do
80− putLog Debug $ "Creating process: " <> T.pack (show $ cmdspec p)
124+ putLog Debug $ "Creating process: " <> reconstructCommand (cmdspec p)
81125 liftIO $ Process.createProcess p
82126
83127-- | Like `System.Process.createProcess_` but also logs (debug) the process being run
84128createProcess_
85− :: (MonadIO m, Cli m)
129+ :: (MonadIO m, CliLog m)
86130 => String -> CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
87131createProcess_ 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)
89133 liftIO $ Process.createProcess p
90134
91135-- | Like `System.Process.callProcess` but also logs (debug) the process being run
92136callProcess
93− :: (MonadIO m, Cli m)
137+ :: (MonadIO m, CliLog m)
94138 => String -> [String] -> m ()
95139callProcess exe args = do
96140 putLog Debug $ "Calling process " <> T.pack exe <> " with args: " <> T.pack (show args)
@@ -98,14 +142,14 @@ callProcess exe args = do
98142
99143-- | Like `System.Process.callCommand` but also logs (debug) the command being run
100144callCommand
101− :: (MonadIO m, Cli m)
145+ :: (MonadIO m, CliLog m)
102146 => String -> m ()
103147callCommand cmd = do
104148 putLog Debug $ "Calling command " <> T.pack cmd
105149 liftIO $ Process.callCommand cmd
106150
107151withProcess
108− :: (MonadIO m, MonadMask m, Cli m)
152+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
109153 => CreateProcess -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
110154withProcess process f = do -- TODO: Use bracket.
111155 -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
@@ -116,7 +160,7 @@ withProcess process f = do -- TODO: Use bracket.
116160
117161 liftIO (waitForProcess p) >>= \case
118162 ExitSuccess -> return (out, err)
119− ExitFailure code -> throwM $ ProcessFailed (cmdspec process) code
163+ ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (cmdspec process) code
120164
121165-- Create an input stream from the file handle, associating each item with the given severity.
122166streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
@@ -124,9 +168,17 @@ streamHandle sev = Streams.map (sev,) <=< handleToInputStream
124168
125169-- | Read from an input stream and log its contents
126170streamToLog
127− :: (MonadIO m, MonadMask m, Cli m)
171+ :: (MonadIO m, CliLog m)
128172 => InputStream (Severity, BSC.ByteString) -> m ()
129173streamToLog stream = fix $ \loop -> do
130174 liftIO (Streams.read stream) >>= \case
131175 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))
2424
2525import Obelisk.CliApp.Logging (allowUserToMakeLoggingVerbose, fork, putLog)
2626import 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)
2828
2929-- | Run an action with a CLI spinner.
3030withSpinner
31− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
31+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
3232 => Text -> m a -> m a
3333withSpinner s = withSpinner' s $ Just $ const s
3434
@@ -39,13 +39,13 @@ withSpinner s = withSpinner' s $ Just $ const s
3939-- The 'no trail' property automatically carries over to sub-spinners (in that
4040-- they won't leave a trail either).
4141withSpinnerNoTrail
42− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
42+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
4343 => Text -> m a -> m a
4444withSpinnerNoTrail s = withSpinner' s Nothing
4545
4646-- | Advanced version that controls the display and content of the trail message.
4747withSpinner'
48− :: (MonadIO m, MonadMask m, Cli m, HasCliConfig m)
48+ :: (MonadIO m, MonadMask m, CliLog m, HasCliConfig e m)
4949 => Text
5050 -> Maybe (a -> Text) -- ^ Leave an optional trail with the given message creator
5151 -> m a
lib/cliapp/src/Obelisk/CliApp/Types.hs+94−18
@@ -1,27 +1,30 @@
11{-# LANGUAGE ConstraintKinds #-}
22{-# LANGUAGE FlexibleContexts #-}
33{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE FunctionalDependencies #-}
5+{-# LANGUAGE LambdaCase #-}
46{-# LANGUAGE GeneralizedNewtypeDeriving #-}
57{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE UndecidableInstances #-}
610module Obelisk.CliApp.Types where
711
812import Control.Concurrent.MVar (MVar)
913import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
10−import Control.Monad.Log (LoggingT, MonadLog, Severity (..), WithSeverity (..))
11−import Control.Monad.Reader (MonadIO, ReaderT (..), ask)
14+import Control.Monad.Log (LoggingT, MonadLog, Severity (..), WithSeverity (..), logMessage)
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)
1221import Data.IORef (IORef)
1322import Data.Text (Text)
23+import System.Exit (ExitCode (..), exitWith)
1424
1525import Obelisk.CliApp.TerminalString (TerminalString)
1626
17−data CliConfig = CliConfig
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− }
27+--------------------------------------------------------------------------------
2528
2629data Output
2730 = Output_Log (WithSeverity Text) -- Regular logging message (with colors and newlines)
@@ -31,19 +34,92 @@ data Output
3134 | Output_ClearLine -- Clear the line
3235 deriving (Eq, Show, Ord)
3336
34−type Cli m = MonadLog Output m
37+type CliLog m = MonadLog Output m
3538
36−newtype CliT m a = CliT
37− { unCliT :: ReaderT CliConfig (LoggingT Output m) a
38− }
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+
47+newtype DieT e m a = DieT { unDieT :: ReaderT (e -> (Text, Int)) (LoggingT Output m) a }
3948 deriving
4049 ( Functor, Applicative, Monad, MonadIO
4150 , MonadThrow, MonadCatch, MonadMask
4251 , MonadLog Output
4352 )
4453
45−class Monad m => HasCliConfig m where
46− getCliConfig :: m CliConfig
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+
93+class Monad m => HasCliConfig e m | m -> e where
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
47123
48−instance Monad m => HasCliConfig (CliT m) where
49− getCliConfig = CliT ask
124+instance Monad m => HasCliConfig e (CliT e m)where
125+ getCliConfig = ask
lib/command/obelisk-command.cabal+1−3
@@ -15,7 +15,6 @@ library
1515 , binary
1616 , bytestring
1717 , obelisk-cliapp
18− , obelisk-migration
1918 , containers
2019 , data-default
2120 , directory
@@ -32,6 +31,7 @@ library
3231 , modern-uri
3332 , monad-loops
3433 , mtl
34+ , megaparsec
3535 , network
3636 , network-uri
3737 , optparse-applicative
@@ -52,8 +52,6 @@ library
5252 Obelisk.Command.Project
5353 Obelisk.Command.Run
5454 Obelisk.Command.Thunk
55− Obelisk.Command.Upgrade
56− Obelisk.Command.Upgrade.Hash
5755 Obelisk.Command.Utils
5856 Obelisk.Command.VmBuilder
5957 -- -fobject-code is so that the StaticPointers extension can work in ghci
lib/command/src/Obelisk/App.hs+69−14
@@ -2,40 +2,90 @@
22{-# LANGUAGE FlexibleContexts #-}
33{-# LANGUAGE FlexibleInstances #-}
44{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE LambdaCase #-}
56{-# LANGUAGE MultiParamTypeClasses #-}
67{-# LANGUAGE ScopedTypeVariables #-}
78{-# LANGUAGE StandaloneDeriving #-}
9+{-# LANGUAGE TemplateHaskell #-}
10+{-# LANGUAGE TupleSections #-}
811module Obelisk.App where
912
13+import Control.Lens
1014import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
1115import 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)
1321import System.Directory (XdgDirectory (XdgData), getXdgDirectory)
1422import Control.Monad.Log (MonadLog)
1523
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
1755
1856newtype Obelisk = Obelisk
19− { _obelisk_cliConfig :: CliConfig
57+ { _obelisk_cliConfig :: CliConfig ObeliskError
2058 }
2159
2260newtype ObeliskT m a = ObeliskT
23− { unObeliskT :: ReaderT Obelisk (CliT m) a
61+ { unObeliskT :: ReaderT Obelisk (CliT ObeliskError m) a
2462 }
2563 deriving
2664 ( Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask
27− , HasObelisk, HasCliConfig)
65+ , MonadLog Output -- CliLog
66+ , MonadError ObeliskError -- CliThrow ObeliskError
67+ , HasCliConfig ObeliskError)
2868
29−deriving instance Monad m => MonadLog Output (ObeliskT m)
69+instance MonadTrans ObeliskT where
70+ lift = ObeliskT . lift . lift
3071
31−class HasObelisk m where
72+class Monad m => HasObelisk m where
3273 getObelisk :: m Obelisk
3374
34−instance Monad m => HasObelisk (ReaderT Obelisk m) where
35− getObelisk = ask
75+instance Monad m => HasObelisk (ObeliskT m) where
76+ getObelisk = ObeliskT ask
77+
78+instance HasObelisk m => HasObelisk (ReaderT r m) where
79+ getObelisk = lift getObelisk
3680
37−instance HasCliConfig m => HasCliConfig (ReaderT Obelisk m) where
38− getCliConfig = lift getCliConfig
81+instance (Monoid w, HasObelisk m) => HasObelisk (WriterT w m) where
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
3989
4090runObelisk :: MonadIO m => Obelisk -> ObeliskT m a -> m a
4191runObelisk c =
@@ -43,13 +93,18 @@ runObelisk c =
4393 . flip runReaderT c
4494 . unObeliskT
4595
46−type MonadObelisk m =
47− ( MonadLog Output m
48− , HasCliConfig m
96+type MonadInfallibleObelisk m =
97+ ( CliLog m
98+ , HasCliConfig ObeliskError m
4999 , HasObelisk m
50100 , MonadIO m
51101 , MonadMask m
52102 )
53103
104+type MonadObelisk m =
105+ ( MonadInfallibleObelisk m
106+ , CliThrow ObeliskError m
107+ )
108+
54109getObeliskUserStateDir :: IO FilePath
55110getObeliskUserStateDir = getXdgDirectory XdgData "obelisk"
lib/command/src/Obelisk/Command.hs+55−118
@@ -8,15 +8,13 @@
88module Obelisk.Command where
99
1010import Control.Monad
11−import Control.Monad.Catch (catch)
1211import Control.Monad.IO.Class (liftIO)
1312import qualified Data.Binary as Binary
1413import Data.Bool (bool)
1514import qualified Data.ByteString.Base16 as Base16
1615import qualified Data.ByteString.Lazy as LBS
1716import Data.List
18−import Data.Maybe (catMaybes, listToMaybe)
19−import Data.Text (Text)
17+import Data.Maybe (catMaybes)
2018import qualified Data.Text as T
2119import Data.Text.Encoding
2220import GHC.StaticPtr
@@ -35,11 +33,8 @@ import Obelisk.Command.Deploy
3533import Obelisk.Command.Project
3634import Obelisk.Command.Run
3735import Obelisk.Command.Thunk
38−import Obelisk.Command.Upgrade
39−import Obelisk.Command.Upgrade.Hash (getDirectoryHash, getHashAtGitRevision)
4036import Obelisk.Command.Utils
4137import qualified Obelisk.Command.VmBuilder as VmBuilder
42−import Obelisk.Migration (Hash)
4338
4439
4540data Args = Args
@@ -86,23 +81,21 @@ initSource = foldl1 (<|>)
8681 , InitSource_Symlink <$> strOption (long "symlink" <> action "directory" <> metavar "PATH")
8782 ]
8883
84+initForce :: Parser Bool
85+initForce = switch (long "force" <> help "Allow ob init to overwrite files")
86+
8987data ObCommand
90− = ObCommand_Init InitSource
88+ = ObCommand_Init InitSource Bool
9189 | ObCommand_Deploy DeployCommand
9290 | ObCommand_Run
9391 | ObCommand_Thunk ThunkCommand
9492 | ObCommand_Repl
9593 | ObCommand_Watch
96− | ObCommand_Upgrade (Maybe Text)
9794 | ObCommand_Internal ObInternal
9895 deriving Show
9996
10097data ObInternal
10198 = ObInternal_RunStaticIO StaticKey
102− | ObInternal_Hash (Maybe Text)
103− | ObInternal_CreateMigration
104− | ObInternal_VerifyMigration
105− | ObInternal_Migrate Hash
10699 | ObInternal_CLIDemo
107100 deriving Show
108101
@@ -127,13 +120,12 @@ inNixShell' p = withProjectRoot "." $ \root -> do
127120obCommand :: ArgsConfig -> Parser ObCommand
128121obCommand cfg = hsubparser
129122 (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"
131124 , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project"
132125 , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode"
133126 , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories"
134127 , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter"
135128 , 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"
137129 ])
138130 <|> subparser
139131 (mconcat
@@ -179,7 +171,6 @@ deployInitOpts = DeployInitOpts
179171 <*> strOption (long "route" <> metavar "PUBLICROUTE" <> help "Publicly accessible URL of your app")
180172 <*> strOption (long "admin-email" <> metavar "ADMINEMAIL" <> help "Email address where administrative alerts will be sent")
181173 <*> 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)
183174
184175type TeamID = String
185176data PlatformDeployment = Android | IOS TeamID
@@ -202,17 +193,12 @@ data DeployInitOpts = DeployInitOpts
202193 , _deployInitOpts_route :: String
203194 , _deployInitOpts_adminEmail :: String
204195 , _deployInitOpts_enableHttps :: Bool
205− , _deployInitOpts_remote :: String
206196 }
207197 deriving Show
208198
209199internalCommand :: Parser ObInternal
210200internalCommand = subparser $ mconcat
211201 [ 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"
216202 , command "clidemo" $ info (pure ObInternal_CLIDemo) mempty
217203 ]
218204
@@ -224,32 +210,17 @@ thunkDirectoryParser = fmap (dropTrailingPathSeparator . normalise) . strArgumen
224210 , help "Path to directory containing thunk data"
225211 ]
226212
227−data ThunkPackOpts = ThunkPackOpts
228− { _thunkPackOpts_directory :: FilePath
229− , _thunkPackOpts_upstream :: String
230− }
231− deriving Show
232−
233213data ThunkCommand
234214 = ThunkCommand_Update [FilePath]
235215 | ThunkCommand_Unpack [FilePath]
236− | ThunkCommand_Pack [ThunkPackOpts]
216+ | ThunkCommand_Pack [FilePath]
237217 deriving Show
238218
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−
248219thunkCommand :: Parser ThunkCommand
249220thunkCommand = hsubparser $ mconcat
250221 [ command "update" $ info (ThunkCommand_Update <$> some thunkDirectoryParser) $ progDesc "Update thunk to latest revision available"
251222 , 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"
253224 ]
254225
255226parserPrefs :: ParserPrefs
@@ -267,7 +238,22 @@ mkObeliskConfig = do
267238 -- This function should not use argument parser (full argument parsing happens post handoff)
268239 let logLevel = toLogLevel $ "-v" `elem` cliArgs
269240 notInteractive <- not <$> isInteractiveTerm
270− cliConf <- newCliConfig logLevel notInteractive notInteractive
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
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+
271257 return $ Obelisk cliConf
272258 where
273259 toLogLevel = bool Notice Debug
@@ -305,71 +291,33 @@ main' argsCfg = do
305291 , "logging-level=" <> show logLevel
306292 ]
307293
308− (mainWithHandOff argsCfg <=< parseHandoff) =<< liftIO getArgs
309− `catch`
310− \(ProcessFailed p code) -> failWith $ "Process exited with code " <> tshow code <> "; " <> tshow p
311−
312−-- Type representing the result of a handoff calculation.
313−data HandOff m
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
337− False -> return ()
338− True -> putLog Warning $
339− "Ignoring unexpected --no-handoff (should only be passed once and as the first argument)"
340−
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
347− Nothing -> do
348− putLog Debug "Not in a project; no need to hand off"
349− pure $ HandOff_No as
350− Just impl -> case getSubCommand as of
351− Just "upgrade" ->
352− pure $ HandOff_Decide (decideHandOffToProjectOb ".") impl as
353− _ ->
354− pure $ HandOff_Yes impl as
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
360− (True, xs)
361− x:xs
362− -- Otherwise bash completion would always hand-off even if the user isn't trying to
363− | "--bash-completion" `isPrefixOf` x && "--no-handoff" `elem` xs ->
364− (True, x:xs)
365− | otherwise ->
366− (False, x:xs)
367− xs ->
368− (False, xs)
294+ --TODO: We'd like to actually use the parser to determine whether to hand off,
295+ --but in the case where this implementation of 'ob' doesn't support all
296+ --arguments being passed along, this could fail. For now, we don't bother
297+ --with optparse-applicative until we've done the handoff.
298+ let go as = do
299+ args' <- liftIO $ handleParseResult (execParserPure parserPrefs (argsInfo argsCfg) as)
300+ case _args_noHandOffPassed args' of
301+ False -> return ()
302+ True -> putLog Warning "--no-handoff should only be passed once and as the first argument; ignoring"
303+ ob $ _args_command args'
304+ handoffAndGo as = findProjectObeliskCommand "." >>= \case
305+ Nothing -> go as -- If not in a project, just run ourselves
306+ Just impl -> do
307+ -- Invoke the real implementation, using --no-handoff to prevent infinite recursion
308+ putLog Debug $ "Handing off to " <> T.pack impl
309+ liftIO $ executeFile impl False ("--no-handoff" : myArgs) Nothing
310+ case myArgs of
311+ "--no-handoff" : as -> go as -- If we've been told not to hand off, don't hand off
312+ a:as -- Otherwise bash completion would always hand-off even if the user isn't trying to
313+ | "--bash-completion" `isPrefixOf` a
314+ && "--no-handoff" `elem` as -> go (a:as)
315+ | otherwise -> handoffAndGo (a:as)
316+ as -> handoffAndGo as
369317
370318ob :: MonadObelisk m => ObCommand -> m ()
371319ob = \case
372− ObCommand_Init source -> initProject source
320+ ObCommand_Init source force -> initProject source force
373321 ObCommand_Deploy dc -> case dc of
374322 DeployCommand_Init deployOpts -> withProjectRoot "." $ \root -> do
375323 let deployDir = _deployInitOpts_outputDir deployOpts
@@ -387,7 +335,7 @@ ob = \case
387335 Right (ThunkData_Packed ptr) -> return ptr
388336 Right (ThunkData_Checkout (Just ptr)) -> return ptr
389337 Right (ThunkData_Checkout Nothing) ->
390− getThunkPtr' False root (T.pack $ _deployInitOpts_remote deployOpts)
338+ getThunkPtr' False root
391339 let sshKeyPath = _deployInitOpts_sshKey deployOpts
392340 hostname = _deployInitOpts_hostname deployOpts
393341 route = _deployInitOpts_route deployOpts
@@ -395,9 +343,11 @@ ob = \case
395343 enableHttps = _deployInitOpts_enableHttps deployOpts
396344 deployInit thunkPtr (root </> "config") deployDir
397345 sshKeyPath hostname route adminEmail enableHttps
398− DeployCommand_Push remoteBuilder -> deployPush "." $ case remoteBuilder of
399− Nothing -> pure []
400− Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
346+ DeployCommand_Push remoteBuilder -> do
347+ deployPath <- liftIO $ canonicalizePath "."
348+ deployPush deployPath $ case remoteBuilder of
349+ Nothing -> pure []
350+ Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
401351 DeployCommand_Update -> deployUpdate "."
402352 DeployCommand_Test Android -> deployMobile "android" []
403353 DeployCommand_Test (IOS teamID) -> deployMobile "ios" [teamID]
@@ -406,28 +356,15 @@ ob = \case
406356 ObCommand_Thunk tc -> case tc of
407357 ThunkCommand_Update thunks -> mapM_ updateThunkToLatest thunks
408358 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
410360 ObCommand_Repl -> runRepl
411361 ObCommand_Watch -> inNixShell' $ static runWatch
412− ObCommand_Upgrade branch -> do
413− upgradeObelisk "." branch
414362 ObCommand_Internal icmd -> case icmd of
415363 ObInternal_RunStaticIO k -> liftIO (unsafeLookupStaticPtr @(ObeliskT IO ()) k) >>= \case
416364 Nothing -> failWith $ "ObInternal_RunStaticIO: no such StaticKey: " <> T.pack (show k)
417365 Just p -> do
418366 c <- getObelisk
419367 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
431368 ObInternal_CLIDemo -> cliDemo
432369
433370--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
113113 checkGitCleanStatus srcPath True >>= \case
114114 True -> do
115115 result <- build
116− packThunk srcPath "origin" -- get upstream
116+ packThunk srcPath
117117 return result
118118 False -> do
119119 _ <- 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 #-}
13{-# LANGUAGE FlexibleContexts #-}
24{-# LANGUAGE LambdaCase #-}
35{-# LANGUAGE MultiParamTypeClasses #-}
46{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE Rank2Types #-}
58{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TypeApplications #-}
610module Obelisk.Command.Nix
711 ( nixBuild
812 , withNixRemoteCheck
@@ -15,7 +19,9 @@ module Obelisk.Command.Nix
1519 , strArg
1620 ) where
1721
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)
1925import Control.Monad.IO.Class (liftIO)
2026import Data.Bool (bool)
2127import Data.Default
@@ -26,7 +32,7 @@ import System.Directory
2632import System.Environment
2733import System.Process (proc)
2834
29−import Obelisk.App (MonadObelisk)
35+import Obelisk.App (MonadInfallibleObelisk, MonadObelisk, ObeliskError (..))
3036import Obelisk.CliApp
3137
3238-- | Where to put nix-build output
@@ -81,8 +87,11 @@ instance Default NixBuildConfig where
8187
8288nixBuild :: MonadObelisk m => NixBuildConfig -> m FilePath
8389nixBuild 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
8591 [[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
8695 where
8796 path = _target_path $ _nixBuildConfig_target cfg
8897 attr = _target_attr $ _nixBuildConfig_target cfg
@@ -103,19 +112,27 @@ nixBuild cfg = withSpinner' ("Running nix-build on " <> desc) (Just $ const $ "B
103112-- tell the user what to do.
104113--
105114-- Added: June, 2018. Consider removing this eventually.
106−withNixRemoteCheck :: MonadObelisk m => m a -> m a
107−withNixRemoteCheck f = f `catch` \e@(ProcessFailed _ _) -> do
108− liftIO (lookupEnv "NIX_REMOTE") >>= \case
109− Just _ -> throwM e
110− Nothing -> liftIO (writable <$> getPermissions "/nix/var/nix/db") >>= \case
111− True -> throwM e
112− False -> do
113− putLog Error "!!! "
114− putLog Error "!!! 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:"
116− putLog Error "!!! "
117− putLog Error "!!! export NIX_REMOTE=daemon"
118− putLog Error "!!! "
119− putLog Error "!!! For details, see https://github.com/NixOS/nixpkgs/issues/5713"
120− putLog Error "!!! "
121− throwM e
115+withNixRemoteCheck
116+ :: MonadObelisk m
117+ => (forall m'. MonadInfallibleObelisk m' => ExceptT ObeliskError m' a)
118+ -> m a
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
125+ Right pf -> liftIO (lookupEnv "NIX_REMOTE") >>= \case
126+ Just _ -> pure e
127+ Nothing -> liftIO (writable <$> getPermissions "/nix/var/nix/db") >>= \case
128+ True -> pure e
129+ False -> pure $ ObeliskError_ProcessError pf $ Just $ T.unlines
130+ [ "!!! "
131+ , "!!! A nix command failed to run. You might need to set the NIX_REMOTE environment variable"
132+ , "!!! to `daemon`. To do this, run the following before running obelisk:"
133+ , "!!! "
134+ , "!!! export NIX_REMOTE=daemon"
135+ , "!!! "
136+ , "!!! For details, see https://github.com/NixOS/nixpkgs/issues/5713"
137+ , "!!! "
138+ ]
lib/command/src/Obelisk/Command/Project.hs+7−4
@@ -48,7 +48,6 @@ obeliskSourceWithBranch branch = ThunkSource_GitHub $ GitHubSource
4848 { _gitHubSource_owner = "obsidiansystems"
4949 , _gitHubSource_repo = "obelisk"
5050 , _gitHubSource_branch = Just branch
51− , _gitHubSource_private = False
5251 }
5352
5453data InitSource
@@ -66,10 +65,14 @@ toImplDir :: FilePath -> FilePath
6665toImplDir p = toObeliskDir p </> "impl"
6766
6867-- | Create a new project rooted in the current directory
69−initProject :: MonadObelisk m => InitSource -> m ()
70−initProject source = withSystemTempDirectory "ob-init" $ \tmpDir -> do
68+initProject :: MonadObelisk m => InitSource -> Bool -> m ()
69+initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
7170 let implDir = toImplDir tmpDir
7271 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."
7376 skeleton <- withSpinner "Setting up obelisk" $ do
7477 liftIO $ createDirectory obDir
7578 case source of
@@ -110,7 +113,7 @@ initProject source = withSystemTempDirectory "ob-init" $ \tmpDir -> do
110113--TODO: Allow the user to ignore our security concerns
111114-- | Find the Obelisk implementation for the project at the given path
112115findProjectObeliskCommand :: MonadObelisk m => FilePath -> m (Maybe FilePath)
113−findProjectObeliskCommand target = withSpinnerNoTrail "Locating project obelisk" $ do
116+findProjectObeliskCommand target = do
114117 myUid <- liftIO getRealUserID
115118 targetStat <- liftIO $ getFileStatus target
116119 (result, insecurePaths) <- flip runStateT [] $ walkToProjectRoot target targetStat myUid >>= \case
lib/command/src/Obelisk/Command/Run.hs+3−3
@@ -51,7 +51,7 @@ run = do
5151 pkgs <- getLocalPkgs
5252 withGhciScript pkgs $ \dotGhciPath -> do
5353 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"]
5555
5656runRepl :: MonadObelisk m => m ()
5757runRepl = do
@@ -112,7 +112,7 @@ parseCabalPackage dir = do
112112 mapM_ (putLog Error) $ fmap (T.pack . show) errors
113113 return Nothing
114114
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
116116withUTF8FileContentsM fp f = do
117117 c <- getCliConfig
118118 liftIO $ withUTF8FileContents fp $ runCli c . f . toUTF8BS
@@ -173,7 +173,7 @@ runGhcid dotGhci mcmd = callCommand $ unwords $ "ghcid" : opts
173173 opts =
174174 [ "-W"
175175 --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 <> "' "
177177 , "--reload=config"
178178 , "--outputfile=ghcid-output.txt"
179179 ] <> testCmd
lib/command/src/Obelisk/Command/Thunk.hs+345−265
@@ -27,10 +27,13 @@ module Obelisk.Command.Thunk
2727 , updateThunk
2828 , getThunkPtr
2929 , getThunkPtr'
30+ , parseGitUri
31+ , uriThunkPtr
3032 ) where
3133
3234import Control.Applicative
33−import Control.Exception (displayException, throwIO, try)
35+import Control.Exception (displayException, try)
36+import Control.Lens.Indexed hiding ((<.>))
3437import Control.Monad
3538import Control.Monad.Catch (handle)
3639import Control.Monad.Except
@@ -39,15 +42,14 @@ import qualified Data.Aeson as Aeson
3942import Data.Aeson.Encode.Pretty
4043import qualified Data.Aeson.Types as Aeson
4144import qualified Data.ByteString.Lazy as LBS
42−import qualified Data.ByteString.Lazy.Char8 as LBSC
4345import Data.Default
44−import Data.Either.Combinators (rightToMaybe)
45−import Data.Foldable
46+import Data.Either.Combinators (fromRight', rightToMaybe)
4647import Data.Git.Ref (Ref)
4748import qualified Data.Git.Ref as Ref
4849import qualified Data.List as L
4950import Data.List.NonEmpty (NonEmpty (..))
5051import qualified Data.List.NonEmpty as NonEmpty
52+import Data.Map (Map)
5153import qualified Data.Map as Map
5254import Data.Maybe
5355import Data.Set (Set)
@@ -57,12 +59,9 @@ import qualified Data.Text as T
5759import Data.Text.Encoding
5860import qualified Data.Text.IO as T
5961import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
60−import Data.Yaml (parseMaybe, (.:))
61−import qualified Data.Yaml as Yaml
62+import Data.Yaml (parseMaybe)
6263import GitHub
6364import GitHub.Data.Name
64−import GitHub.Endpoints.Repos.Contents (archiveForR)
65−import Network.URI
6665import Obelisk.Command.Nix
6766import System.Directory
6867import System.FilePath
@@ -70,6 +69,7 @@ import System.IO.Error
7069import System.IO.Temp
7170import System.Posix (getSymbolicLinkStatus, modificationTime)
7271import System.Process (proc)
72+import Text.URI
7373
7474import Obelisk.App (MonadObelisk)
7575import Obelisk.CliApp
@@ -112,97 +112,73 @@ data GitHubSource = GitHubSource
112112 { _gitHubSource_owner :: Name Owner
113113 , _gitHubSource_repo :: Name Repo
114114 , _gitHubSource_branch :: Maybe (Name Branch)
115− , _gitHubSource_private :: Bool
116115 }
117116 deriving (Show, Eq, Ord)
118117
119118data GitSource = GitSource
120119 { _gitSource_url :: URI
121− , _gitSource_branch :: Name Branch
120+ , _gitSource_branch :: Maybe (Name Branch)
122121 , _gitSource_fetchSubmodules :: Bool
123122 }
124123 deriving (Show, Eq, Ord)
125124
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+
126148getThunkGitBranch :: ThunkPtr -> Maybe Text
127149getThunkGitBranch (ThunkPtr _ src) = fmap untagName $ case src of
128150 ThunkSource_GitHub s -> _gitHubSource_branch s
129− ThunkSource_Git s -> Just $ _gitSource_branch s
151+ ThunkSource_Git s -> _gitSource_branch s
130152
131153commitNameToRef :: Name Commit -> Ref
132154commitNameToRef (N c) = Ref.fromHex $ encodeUtf8 c
133155
134156-- TODO: Use spinner here.
135−getNixSha256ForUriUnpacked :: MonadObelisk m => URI -> m NixSha256
157+getNixSha256ForUriUnpacked
158+ :: MonadObelisk m
159+ => URI
160+ -> m NixSha256
136161getNixSha256ForUriUnpacked uri =
137− withExitFailMessage ("nix-prefetch-url: Failed to determine sha256 hash of URL " <> T.pack (show uri)) $ do
138− fmap T.pack $ withNixRemoteCheck $ readProcessAndLogStderr Debug $
139− proc "nix-prefetch-url" ["--unpack" , "--type" , "sha256" , show uri]
162+ withExitFailMessage ("nix-prefetch-url: Failed to determine sha256 hash of URL " <> render uri) $ do
163+ [hash] <- fmap T.lines $ withNixRemoteCheck $ readProcessAndLogOutput (Debug, Debug) $
164+ proc "nix-prefetch-url" ["--unpack" , "--type" , "sha256" , T.unpack $ render uri]
165+ pure hash
140166
141167nixPrefetchGit :: MonadObelisk m => URI -> Text -> Bool -> m NixSha256
142168nixPrefetchGit 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
144170 out <- withNixRemoteCheck $ readProcessAndLogStderr Debug $
145171 proc "nix-prefetch-git" $ filter (/="")
146− [ "--url", show uri
172+ [ "--url", T.unpack $ render uri
147173 , "--rev", T.unpack rev
148174 , if fetchSubmodules then "--fetch-submodules" else ""
149175 , "--quiet"
150176 ]
151177
152− case parseMaybe (Aeson..: "sha256") =<< Aeson.decode (LBSC.pack out) of
153− Nothing -> failWith $ "nix-prefetch-git: unrecognized output " <> T.pack out
178+ case parseMaybe (Aeson..: "sha256") =<< Aeson.decodeStrict (encodeUtf8 out) of
179+ Nothing -> failWith $ "nix-prefetch-git: unrecognized output " <> out
154180 Just x -> pure x
155181
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−
206182--TODO: Pretty print these
207183data ReadThunkError
208184 = ReadThunkError_UnrecognizedFiles
@@ -311,18 +287,16 @@ parseGitHubSource v = do
311287 owner <- v Aeson..: "owner"
312288 repo <- v Aeson..: "repo"
313289 branch <- v Aeson..:! "branch"
314− mPrivate <- v Aeson..:! "private"
315290 pure $ GitHubSource
316291 { _gitHubSource_owner = owner
317292 , _gitHubSource_repo = repo
318293 , _gitHubSource_branch = branch
319− , _gitHubSource_private = fromMaybe False mPrivate
320294 }
321295
322296parseGitSource :: Aeson.Object -> Aeson.Parser GitSource
323297parseGitSource v = do
324298 Just url <- parseGitUri <$> v Aeson..: "url"
325− branch <- v Aeson..: "branch"
299+ branch <- v Aeson..:! "branch"
326300 fetchSubmodules <- v Aeson..:! "fetchSubmodules"
327301 pure $ GitSource
328302 { _gitSource_url = url
@@ -351,20 +325,14 @@ encodeThunkPtrData (ThunkPtr rev src) = case src of
351325 ThunkSource_GitHub s -> encodePretty' githubCfg $ Aeson.object $ catMaybes
352326 [ Just $ "owner" .= _gitHubSource_owner s
353327 , Just $ "repo" .= _gitHubSource_repo s
354− , case _gitHubSource_branch s of
355− Just b -> Just $ "branch" .= b
356− Nothing -> Nothing
357− , if _gitHubSource_private s
358− then Just $ "private" .= True
359− else Nothing
328+ , ("branch" .=) <$> _gitHubSource_branch s
360329 , Just $ "rev" .= Ref.toHexString (_thunkRev_commit rev)
361330 , Just $ "sha256" .= _thunkRev_nixSha256 rev
362331 ]
363332 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)
366334 , Just $ "rev" .= Ref.toHexString (_thunkRev_commit rev)
367− , Just $ "branch" .= _gitSource_branch s
335+ , ("branch" .=) <$> _gitSource_branch s
368336 , Just $ "sha256" .= _thunkRev_nixSha256 rev
369337 , Just $ "fetchSubmodules" .= _gitSource_fetchSubmodules s
370338 ]
@@ -496,20 +464,6 @@ plainGitStandaloneLoaderV2 = T.unlines
496464thunkFileNames :: [FilePath]
497465thunkFileNames = ["default.nix", "github.json", "git.json"]
498466
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−
513467--TODO: when checking something out, make a shallow clone
514468
515469-- | Checks a cache directory to see if there is a fresh symlink
@@ -588,7 +542,7 @@ updateThunk p f = withSystemTempDirectory "obelisk-thunkptr-" $ \tmpDir -> do
588542 p' <- copyThunkToTmp tmpDir p
589543 unpackThunk' True p'
590544 result <- f p'
591− updateThunkFromTmp p' "origin" -- TODO: stop hardcoding remote
545+ updateThunkFromTmp p'
592546 return result
593547 where
594548 copyThunkToTmp tmpDir thunkDir = readThunk thunkDir >>= \case
@@ -599,8 +553,8 @@ updateThunk p f = withSystemTempDirectory "obelisk-thunkptr-" $ \tmpDir -> do
599553 proc "cp" ["-r", "-T", thunkDir, tmpThunk]
600554 return tmpThunk
601555 Right _ -> failWith $ "Thunk is not packed"
602− updateThunkFromTmp p' remote = do
603− packThunk' True p' remote
556+ updateThunkFromTmp p' = do
557+ packThunk' True p'
604558 callProcessAndLogOutput (Notice, Error) $
605559 proc "cp" ["-r", "-T", p', p]
606560
@@ -619,190 +573,316 @@ unpackThunk' noTrail thunkDir = readThunk thunkDir >>= \case
619573 let (thunkParent, thunkName) = splitFileName thunkDir
620574 withTempDirectory thunkParent thunkName $ \tmpRepo -> do
621575 let obGitDir = tmpRepo </> ".git" </> "obelisk"
622− case _thunkPtr_source tptr of
623− ThunkSource_GitHub s -> do
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
656− withSpinner' ("Fetching thunk " <> T.pack thunkName)
657− (finalMsg noTrail $ const $ "Fetched thunk " <> T.pack thunkName) $ do
658− let git = callProcessAndLogOutput (Notice, Notice) . gitProc tmpRepo
659− git ["clone", if _gitSource_fetchSubmodules s then "--recursive" else "", show (_gitSource_url s)]
660− git ["reset", "--hard", Ref.toHexString $ _thunkRev_commit $ _thunkPtr_rev tptr]
661− when (_gitSource_fetchSubmodules s) $
662− git ["submodule", "update", "--recursive", "--init"]
663− git ["branch", "-u", "origin/" <> T.unpack (untagName $ _gitSource_branch s)]
664−
665− liftIO $ createDirectory obGitDir
666− callProcessAndLogOutput (Notice, Error) $
667− proc "cp" ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
668− callProcessAndLogOutput (Notice, Error) $
669− proc "rm" ["-r", thunkDir]
670− callProcessAndLogOutput (Notice, Error) $
671− proc "mv" ["-T", tmpRepo, thunkDir]
576+ s = case _thunkPtr_source tptr of
577+ ThunkSource_GitHub s' -> forgetGithub False s'
578+ ThunkSource_Git s' -> s'
579+ withSpinner' ("Fetching thunk " <> T.pack thunkName)
580+ (finalMsg noTrail $ const $ "Fetched thunk " <> T.pack thunkName) $ do
581+ let git = callProcessAndLogOutput (Notice, Notice) . gitProc tmpRepo
582+ git $ [ "clone" ]
583+ ++ ("--recursive" <$ guard (_gitSource_fetchSubmodules s))
584+ ++ [ T.unpack $ render $ _gitSource_url s ]
585+ git ["reset", "--hard", Ref.toHexString $ _thunkRev_commit $ _thunkPtr_rev tptr]
586+ when (_gitSource_fetchSubmodules s) $
587+ git ["submodule", "update", "--recursive", "--init"]
588+ case _gitSource_branch s of
589+ Just b -> git ["branch", "-u", "origin/" <> T.unpack (untagName $ b)]
590+ Nothing -> pure ()
591+
592+ liftIO $ createDirectory obGitDir
593+ callProcessAndLogOutput (Notice, Error) $
594+ proc "cp" ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
595+ callProcessAndLogOutput (Notice, Error) $
596+ proc "rm" ["-r", thunkDir]
597+ callProcessAndLogOutput (Notice, Error) $
598+ proc "mv" ["-T", tmpRepo, thunkDir]
672599
673600--TODO: add force mode to pack even if changes are present
674601--TODO: add a rollback mode to pack to the original thunk
675−packThunk :: MonadObelisk m => FilePath -> Text -> m ()
602+packThunk :: MonadObelisk m => FilePath -> m ()
676603packThunk = packThunk' False
677604
678−packThunk' :: MonadObelisk m => Bool -> FilePath -> Text -> m ()
679−packThunk' noTrail thunkDir upstream = readThunk thunkDir >>= \case
605+packThunk' :: MonadObelisk m => Bool -> FilePath -> m ()
606+packThunk' noTrail thunkDir = readThunk thunkDir >>= \case
680607 Left err -> failWith $ T.pack $ "thunk pack: " <> show err
681608 Right (ThunkData_Packed _) -> failWith "pack: thunk is already packed"
682609 Right (ThunkData_Checkout _) -> do
683610 withSpinner' ("Packing thunk " <> T.pack thunkDir)
684611 (finalMsg noTrail $ const $ "Packed thunk " <> T.pack thunkDir) $ do
685− thunkPtr <- getThunkPtr thunkDir upstream
612+ thunkPtr <- getThunkPtr thunkDir
686613 callProcessAndLogOutput (Debug, Error) $ proc "rm" ["-rf", thunkDir]
687614 liftIO $ createThunk thunkDir thunkPtr
688615
689−getThunkPtr :: MonadObelisk m => FilePath -> Text -> m ThunkPtr
616+getThunkPtr :: MonadObelisk m => FilePath -> m ThunkPtr
690617getThunkPtr = getThunkPtr' True
691618
692−getThunkPtr' :: MonadObelisk m => Bool -> FilePath -> Text -> m ThunkPtr
693−getThunkPtr' checkClean thunkDir upstream = do
694− when checkClean $ ensureCleanGitRepo thunkDir True $
695− "thunk pack: thunk checkout contains unsaved modifications"
696−
697− -- Check whether there are any stashes
698− stashOutput <- readGitProcess thunkDir ["stash", "list"]
699− when checkClean $ case T.null stashOutput of
700− False -> do
701− failWith $ T.unlines $
702− [ "thunk pack: thunk checkout has stashes"
703− , "git stash list:"
704− ] ++ T.lines stashOutput
705− True -> return ()
706−
707− --Check whether all local heads are pushed
708− repoHeads <- T.lines <$> readGitProcess thunkDir ["for-each-ref", "--format=%(refname:short)", "refs/heads/"]
709− remotes <- T.lines <$> readGitProcess thunkDir ["remote"]
710− --Check that the upstream specified actually exists
711− case L.find (== upstream) remotes of
712− Nothing -> failWith $ "thunk pack: upstream " <> upstream <> " does not exist. Available upstreams are " <> T.intercalate ", " remotes
713− Just _ -> return ()
714− -- iterate over cartesian product
715− when checkClean $ forM_ repoHeads $ \hd -> do
716− [localRev] <- T.lines <$> readGitProcess thunkDir ["rev-parse", T.unpack hd]
717− forM_ remotes $ \rm -> do
718− --TODO: The error you get if a branch isn't pushed anywhere could be made more user friendly
719− [remoteMergeRev] <- fmap T.lines $ readGitProcess thunkDir
720− ["merge-base", T.unpack localRev, "remotes/" <> T.unpack rm <> "/" <> T.unpack hd]
721− case remoteMergeRev == localRev of
722− False -> failWith $ mconcat [ "thunk unpack: branch ", hd, " has not been pushed to ", rm ]
723− True -> return ()
724−
725− --We assume it's safe to pack the thunk at this point
726− [remoteUri'] <- fmap T.lines $ readGitProcess thunkDir ["config", "--get", "remote." <> T.unpack upstream <> ".url"]
727−
728− let uriParseFailure = "Could not identify git remote: " <> remoteUri'
729− mRemoteUri = parseGitUri remoteUri'
730− case mRemoteUri of
731− Nothing -> failWith uriParseFailure
732− Just remoteUri -> do
733− refs <- fmap (map (refsToTuples . T.words) . T.lines) $ readGitProcess thunkDir ["show-ref", "--head"]
734− -- safe because exactly one hash is associated with HEAD
735− let Just currentHead = fst <$> L.find ((== "HEAD") . snd) refs
736− remoteHeads = Map.fromList $ catMaybes $ fmap
737− (\(x,y) -> (,) x <$> T.stripPrefix ("refs/remotes/" <> upstream <> "/") y)
738− refs
739− localHeads = Map.fromList $ catMaybes $ fmap
740− (\(x,y) -> (,) x <$> T.stripPrefix "refs/heads/" y)
741− refs
742− currentUpstreamBranch = Map.lookup currentHead remoteHeads
743− <|> Map.lookup currentHead localHeads
744− if isGithubThunk remoteUri
745− then githubThunkPtr remoteUri currentHead currentUpstreamBranch
746− else gitThunkPtr remoteUri currentHead currentUpstreamBranch
747− where
748− refsToTuples [x, y] = (x, y)
749− refsToTuples x = error $ "thunk pack: cannot parse ref " <> show x
750− isGithubThunk u
751− | Just uriAuth <- uriAuthority u
752− = case uriScheme u of
753− "ssh:" -> uriAuth == URIAuth "git@" "github.com" ""
754− s -> s `L.elem` [ "git:", "https:", "http:" ] -- "http:" just redirects to "https:"
755− && uriRegName uriAuth == "github.com"
756− | otherwise = False
757− githubThunkPtr u commit' branch' = do
758− ["/", owner', repo'] <- return $ splitDirectories (uriPath u)
759− let owner = N (T.pack owner')
760− dropGitExtension r = fromMaybe r $ T.stripSuffix ".git" r
761− repo = N $ dropGitExtension $ T.pack repo'
762− commit = N commit'
763− branch = N <$> branch'
764− mauth <- liftIO $ getHubAuth "github.com"
765− repoResult <- liftIO $ executeRequestMaybe mauth $ repositoryR owner repo
766− repoIsPrivate <- either (liftIO . throwIO) (return . repoPrivate) repoResult
767− archiveResult <- liftIO $ executeRequestMaybe mauth $
768− archiveForR owner repo ArchiveFormatTarball (Just (untagName commit))
769− archiveUri <- either (liftIO . throwIO) return archiveResult
770− hash <- getNixSha256ForUriUnpacked archiveUri
771− return $ ThunkPtr
772− { _thunkPtr_rev = ThunkRev
773− { _thunkRev_commit = commitNameToRef commit
774− , _thunkRev_nixSha256 = hash
775− }
776− , _thunkPtr_source = ThunkSource_GitHub $ GitHubSource
777− { _gitHubSource_owner = owner
778− , _gitHubSource_repo = repo
779− , _gitHubSource_branch = branch
780− , _gitHubSource_private = repoIsPrivate
781− }
782− }
783− gitThunkPtr u commit branch' = do
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?
619+getThunkPtr' :: forall m. MonadObelisk m => Bool -> FilePath -> m ThunkPtr
620+getThunkPtr' checkClean thunkDir = do
621+ when checkClean $ ensureCleanGitRepo thunkDir True $
622+ "thunk pack: thunk checkout contains unsaved modifications"
623+
624+ -- Check whether there are any stashes
625+ stashOutput <- readGitProcess thunkDir ["stash", "list"]
626+ when checkClean $ case T.null stashOutput of
627+ False -> do
628+ failWith $ T.unlines $
629+ [ "thunk pack: thunk checkout has stashes"
630+ , "git stash list:"
631+ ] ++ T.lines stashOutput
632+ True -> return ()
633+
634+ -- Get information on all branches and their (optional) designated upstream
635+ -- correspondents
636+ (headDump :: [Text]) <- T.lines <$> readGitProcess thunkDir
637+ [ "for-each-ref"
638+ , "--format=%(refname:short) %(upstream:short) %(upstream:remotename)"
639+ , "refs/heads/"
640+ ]
641+
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
662+ when checkClean $ do
663+ let untrackedBranches = Map.keys errorMap
664+ when (not $ L.null untrackedBranches) $ failWith $ T.unlines $
665+ [ "thunk pack: Certain branches in the thunk have no upstream branch \
666+ \set. This means don't know to check whether all your work is \
667+ \saved. The offending branches are:"
668+ , ""
669+ , T.unwords untrackedBranches
670+ , ""
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+
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+
716+ [remoteUri'] <- fmap T.lines $ readGitProcess thunkDir
717+ [ "config"
718+ , "--get"
719+ , "remote." <> T.unpack remote <> ".url"
720+ ]
721+
722+ remoteUri <- case parseGitUri remoteUri' of
723+ Nothing -> failWith $ "Could not identify git remote: " <> remoteUri'
724+ Just uri -> pure uri
725+ uriThunkPtr remoteUri mCurrentBranch
726+
727+-- | Get the latest revision available from the given source
728+getLatestRev :: MonadObelisk m => ThunkSource -> m ThunkRev
729+getLatestRev os = do
730+ let gitS = case os of
731+ ThunkSource_GitHub s -> forgetGithub False s
732+ ThunkSource_Git s -> s
733+ (_, commit) <- gitGetCommitBranch (_gitSource_url gitS) (untagName <$> _gitSource_branch gitS)
734+ case os of
735+ ThunkSource_GitHub s -> githubThunkRev s commit
736+ ThunkSource_Git s -> gitThunkRev s commit
737+
738+-- | Convert a URI to a thunk
739+--
740+-- If the URL is a github URL, we try to just download an archive for
741+-- performance. If that doesn't work (e.g. authentication issue), we fall back
742+-- on just doing things the normal way for git repos in general, and save it as
743+-- a regular git thunk.
744+uriThunkPtr :: MonadObelisk m => URI -> Maybe Text -> m ThunkPtr
745+uriThunkPtr uri mbranch = do
746+ (_, commit) <- gitGetCommitBranch uri mbranch
747+ (src, rev) <- case uriToThunkSource uri mbranch of
748+ ThunkSource_GitHub s -> do
749+ rev <- runExceptT $ githubThunkRev s commit
750+ case rev of
751+ Right r -> pure (ThunkSource_GitHub s, r)
752+ Left e -> do
753+ putLog Warning $ "\
754+\Failed to fetch archive from GitHub. This is probably a private repo. \
755+\Falling back on normal fetchgit. Original failure:\n\n"
756+ errorToWarning e
757+ let s' = forgetGithub True s
758+ (,) (ThunkSource_Git s') <$> gitThunkRev s' commit
759+ ThunkSource_Git s -> (,) (ThunkSource_Git s) <$> gitThunkRev s commit
760+ pure $ ThunkPtr
761+ { _thunkPtr_rev = rev
762+ , _thunkPtr_source = src
763+ }
764+
765+-- | N.B. Cannot infer all fields.
766+--
767+-- If the thunk is a GitHub thunk and fails, we do *not* fall back like with
768+-- `uriThunkPtr`. Unlike a plain URL, a thunk src explicitly states which method
769+-- should be employed, and so we respect that.
770+uriToThunkSource :: URI -> Maybe Text -> ThunkSource
771+uriToThunkSource u
772+ | Right uriAuth <- uriAuthority u
773+ , Just scheme <- unRText <$> uriScheme u
774+ , case scheme of
775+ "ssh" -> uriAuth == Authority
776+ { authUserInfo = Just $ UserInfo (fromRight' $ mkUsername "git") Nothing
777+ , authHost = fromRight' $ mkHost "github.com"
778+ , authPort = Nothing
801779 }
802− }
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
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
803877
804878parseGitUri :: 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
806886
807887parseSshShorthand :: Text -> Maybe URI
808888parseSshShorthand uri = do
@@ -817,4 +897,4 @@ parseSshShorthand uri = do
817897 -- This check is used to disambiguate a filepath containing a colon from shorthand
818898 guard $ isNothing (T.findIndex (=='/') authAndHostname)
819899 && 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 ()
102102tidyUpGitWorkingCopy dir = withSpinnerNoTrail "Tidying up git working copy" $ do
103103 ignored <- gitLsFiles dir ["--ignored", "--exclude-standard", "--others"]
104104 untracked <- gitLsFiles dir ["--exclude-standard", "--others"]
105− putLog Debug $ T.pack $ "Ignored: " <> show (length ignored) <> " files."
106− putLog Debug $ T.pack $ "Untracked files:\n" <> unlines untracked
105+ putLog Debug $ "Ignored: " <> T.pack (show $ length ignored) <> " files."
106+ putLog Debug $ "Untracked files:\n" <> T.unlines untracked
107107 withSpinnerNoTrail "Removing untracked and ignored files" $ do
108− forM_ (fmap (dir </>) $ ignored <> untracked) $
108+ forM_ (fmap ((dir </>) . T.unpack) $ ignored <> untracked) $
109109 liftIO . removePathForcibly
110110 -- Empty directories won't be included in these lists. Git doesn't track them
111111 -- So we must delete these separately.
112112 runProc $ proc "find" [dir, "-depth", "-empty", "-type", "d", "-delete"]
113113 where
114− gitLsFiles pwd opts = fmap lines $ readProcessAndLogStderr Error $
114+ gitLsFiles pwd opts = fmap T.lines $ readProc $
115115 (proc "git" $ ["ls-files", "."] <> opts) { cwd = Just pwd }
116−
lib/command/src/Obelisk/Command/Utils.hs+104−6
@@ -1,16 +1,30 @@
11{-# LANGUAGE FlexibleContexts #-}
22{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE MultiWayIf #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
35{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PatternGuards #-}
47module Obelisk.Command.Utils where
58
6−import Control.Applicative (liftA2)
9+import Control.Applicative hiding (many)
10+import Control.Monad.Except
711import Data.Bool (bool)
12+import qualified Text.Megaparsec.Char.Lexer as ML
13+import Data.Bifunctor
14+import Data.Char
15+import Data.Either
816import Data.Semigroup ((<>))
17+import Data.Map (Map)
18+import qualified Data.Map as M
19+import Data.Maybe (maybeToList)
920import Data.Text (Text)
1021import qualified Data.Text as T
22+import Data.Void (Void)
1123import System.Directory (canonicalizePath)
1224import System.Environment (getExecutablePath)
1325import qualified System.Process as P
26+import Text.Megaparsec as MP
27+import Text.Megaparsec.Char as MP
1428
1529import Obelisk.App (MonadObelisk)
1630import Obelisk.CliApp
@@ -25,7 +39,7 @@ checkGitCleanStatus repo withIgnored = do
2539 runGit = readProcessAndLogStderr Debug . gitProc repo
2640 gitStatus = runGit $ ["status", "--porcelain"] <> bool [] ["--ignored"] withIgnored
2741 gitDiff = runGit ["diff"]
28− null <$> liftA2 (<>) gitStatus gitDiff
42+ T.null <$> liftA2 (<>) gitStatus gitDiff
2943
3044-- | Ensure that git repo is clean
3145ensureCleanGitRepo :: MonadObelisk m => FilePath -> Bool -> Text -> m ()
@@ -46,9 +60,11 @@ initGit repo = do
4660 git ["add", "."]
4761 git ["commit", "-m", "Initial commit."]
4862
63+gitProcNoRepo :: [String] -> P.CreateProcess
64+gitProcNoRepo = P.proc "git"
65+
4966gitProc :: FilePath -> [String] -> P.CreateProcess
50−gitProc repo argsRaw =
51− P.proc "git" $ runGitInDir argsRaw
67+gitProc repo = gitProcNoRepo . runGitInDir
5268 where
5369 runGitInDir args' = case filter (not . null) args' of
5470 args@("clone":_) -> args <> [repo]
@@ -60,7 +76,7 @@ copyDir src dest =
6076 (P.proc "cp" ["-a", ".", dest]) { P.cwd = Just src }
6177
6278readGitProcess :: MonadObelisk m => FilePath -> [String] -> m Text
63−readGitProcess repo = fmap T.pack . readProcessAndLogStderr Notice . gitProc repo
79+readGitProcess repo = readProcessAndLogOutput (Debug, Notice) . gitProc repo
6480
6581processToShellString :: FilePath -> [String] -> String
6682processToShellString cmd args = unwords $ map quoteAndEscape (cmd : args)
@@ -76,7 +92,89 @@ runProcSilently = callProcessAndLogOutput (Debug, Debug)
7692
7793-- | A simpler wrapper for CliApp's readProcessAndLogStderr with sensible defaults.
7894readProc :: MonadObelisk m => P.CreateProcess -> m Text
79−readProc = fmap T.pack . readProcessAndLogStderr Error
95+readProc = readProcessAndLogOutput (Debug, Error)
8096
8197tshow :: Show a => a -> Text
8298tshow = 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 @@
11{-# LANGUAGE FlexibleContexts #-}
22{-# LANGUAGE OverloadedStrings #-}
33{-# LANGUAGE QuasiQuotes #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
45module Obelisk.Command.VmBuilder where
56
67import Control.Applicative (liftA2)
7−import Control.Monad (unless)
8−import Control.Monad.Catch (try)
8+import Control.Monad (when, unless)
9+import Control.Monad.Catch (handle)
910import Control.Monad.IO.Class (liftIO)
10−import Data.Either (isRight)
1111import Data.Monoid ((<>))
1212import Data.String (IsString)
1313import Data.String.Here.Uninterpolated (hereLit)
@@ -15,7 +15,7 @@ import Data.Text (Text)
1515import qualified Data.Text as T
1616import qualified Data.Text.IO as T
1717import System.Directory (createDirectoryIfMissing)
18−import System.Exit (ExitCode)
18+import System.Exit (ExitCode(..))
1919import System.FilePath ((<.>), (</>))
2020import qualified System.Info
2121import System.Process (proc)
@@ -28,10 +28,33 @@ import Obelisk.Command.Nix (withNixRemoteCheck)
2828getNixBuildersArg :: MonadObelisk m => m String
2929getNixBuildersArg = do
3030 stateDir <- liftIO getDockerBuilderStateDir
31− exists <- containerExists
31+ let sshIdFile = stateDir </> sshKeyFileName
32+ checkForNixDarwin sshIdFile
33+ exists <- containerExists stateDir
3234 if exists then startContainer else setupNixDocker stateDir
3335 pure $ nixBuildersArgString stateDir
3436
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+
3558-- | String to pass to nix's `--builders` arguments to enable the VM builder.
3659nixBuildersArgString :: FilePath -> String
3760nixBuildersArgString stateDir = unwords [containerName, "x86_64-linux", stateDir </> sshKeyFileName, "1", "1", "kvm"]
@@ -41,14 +64,19 @@ containerName :: IsString str => str
4164containerName = "obelisk-docker-nix-builder"
4265
4366-- | 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
45−containerExists = withExitFailMessage needDockerMsg $ do
46− containerNames <- fmap (map T.strip . T.lines . T.pack) $
67+containerExists :: MonadObelisk m => FilePath -> m Bool
68+containerExists stateDir = handle (\(_ :: IOError) -> failWith needDockerMsg) $ do
69+ containerNames <- fmap (map T.strip . T.lines) $
4770 readProcessAndLogStderr Error $
4871 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
5075 where
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."
76+ needDockerMsg = T.intercalate "\n"
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+ ]
5280
5381-- | SSH port on localhost that connects to the container.
5482containerSshPort :: Int
@@ -74,25 +102,20 @@ setupNixDocker stateDir = withSpinner ("Creating Docker container named " <> con
74102 proc "ssh-keygen" ["-t", "ed25519", "-f", stateDir </> sshKeyFileName, "-P", ""]
75103
76104 -- Build the docker container (which uses the SSH keys in the 'ssh' folder)
77− containerId <- readProcessAndLogStderr Error $
105+ containerId <- fmap T.strip $ readProcessAndLogStderr Error $
78106 proc "docker" ["build", stateDir, "--quiet"]
79107 callProcessAndLogOutput (Debug, Error) $ proc "docker"
80108 [ "run"
81109 , "--restart", "always"
82110 , "--detach"
83111 , "--publish", show containerSshPort <> ":22"
84− , "--name", containerName, containerId
112+ , "--name", T.unpack containerName, T.unpack containerId
85113 ]
86− exists <- containerExists
114+ exists <- containerExists stateDir
87115 unless exists $
88116 failWith $ "Expected to see docker container named " <> containerName <> " but it does not exist."
89117
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
93− unless linuxBuildWorked $ do
94− let sshIdFile = stateDir </> sshKeyFileName
95− failWith $ setupInstructions sshIdFile
118+ testLinuxBuild stateDir
96119
97120-- | The instructions for setting up SSH access to the container for the Nix daemon.
98121setupInstructions :: FilePath -> Text
@@ -129,19 +152,24 @@ getDockerBuilderStateDir :: IO FilePath
129152getDockerBuilderStateDir = liftA2 (</>) getObeliskUserStateDir (pure "nix-docker-builder")
130153
131154-- | Run a test build to see if a Linux build can finish successfully.
132−testLinuxBuild :: MonadObelisk m => FilePath -> m Bool
133−testLinuxBuild stateDir =
134− fmap (isRight :: Either ExitCode () -> Bool) $ try $ withNixRemoteCheck $
135− callProcessAndLogOutput (Debug, Debug) $ proc "nix-build"
136− [ "-E", "(import <nixpkgs> { system = \"x86_64-linux\"; }).writeText \"test\" builtins.currentTime"
137− , "--builders", nixBuildersArgString stateDir
138− ]
139−
140−-- Copied from https://raw.githubusercontent.com/LnL7/nix-docker/bf28b99aebd8e403d2fc2171f4fa8878f857171c/ssh/Dockerfile
155+testLinuxBuild :: MonadObelisk m => FilePath -> m ()
156+testLinuxBuild stateDir
157+ | System.Info.os == "linux" = failWith "Using the docker builder is not necessary on linux."
158+ | otherwise = withNixRemoteCheck $ do
159+ (exitCode, _stdout, stderr) <- readCreateProcessWithExitCode $ proc "nix-build"
160+ [ "-E", "(import <nixpkgs> { system = \"x86_64-linux\"; }).writeText \"test\" builtins.currentTime"
161+ , "--builders", nixBuildersArgString stateDir
162+ ]
163+ unless (exitCode == ExitSuccess) $ do
164+ let sshIdFile = stateDir </> sshKeyFileName
165+ putLog Error $ T.pack stderr
166+ failWith $ setupInstructions sshIdFile
167+
168+-- Copied from https://github.com/LnL7/nix-docker/blob/8dcfb3aff1f87cdafeecb0d27964b27c3fb8b1d2/ssh/Dockerfile
141169-- Renamed "insecure_rsa" to 'sshKeyFileName'
142170dockerfile :: Text
143171dockerfile = [hereLit|
144−FROM lnl7/nix:2018-04-17
172+FROM lnl7/nix:2018-09-21
145173
146174RUN nix-env -f '<nixpkgs>' -iA \
147175 gnused \
@@ -166,7 +194,8 @@ RUN mkdir -p /etc/ssh \
166194 ] <> [hereLit|
167195
168196EXPOSE 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+
170199|]
171200
172201sshKeyFileName :: FilePath
lib/executable-config/default.nix+2−1
@@ -7,7 +7,8 @@
77}:
88let injectConfig = config: assets: nixpkgs.runCommand "inject-config" {} ''
99 set -x
10− cp -a "${assets}" $out
10+ mkdir -p $out
11+ cp --no-preserve=mode -Lr "${assets}" $out/static
1112 chmod +w "$out"
1213 if ! mkdir $out/config; then
1314 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)
3131import Data.Dependent.Sum (DSum (..))
3232import Data.Functor.Sum
3333import Data.IORef
34−import Data.Monoid hiding (Sum)
35−import Data.Text (Text)
3634import GHCJS.DOM hiding (bracket, catch)
3735import GHCJS.DOM.Document
3836import GHCJS.DOM.Node
@@ -64,13 +62,12 @@ type ObeliskWidget t x route m =
6462 , MonadFix (Performable m)
6563 , PrimMonad m
6664 , Prerender x m
67− , EventWriter t (Endo route) m
65+ , SetRoute t route m
6866 )
6967
7068data Frontend route = Frontend
7169 { _frontend_head :: !(forall t m x. ObeliskWidget t x route m => RoutedT t route m ())
7270 , _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`
7471 }
7572
7673type Widget' x = ImmediateDomBuilderT DomTimeline (DomCoreWidget x)
@@ -130,9 +127,12 @@ runWithHeadAndBody app = withJSContextSingletonMono $ \jsSing -> do
130127 _ -> return ()
131128 return postBuildTriggerRef
132129
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 ()
134131runFrontend 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
136136 runMyRouteViewT
137137 :: ( TriggerEvent t m
138138 , PerformEvent t m
@@ -142,24 +142,22 @@ runFrontend validFullEncoder frontend = do
142142 , MonadFix m
143143 , MonadFix (Performable m)
144144 )
145− => RoutedT t (R route) (EventWriterT t (Endo (R route)) m) a
145+ => RoutedT t (R route) (SetRouteT t (R route) m) a
146146 -> m a
147− runMyRouteViewT = runRouteViewT
148− ve
149− (_frontend_notFoundRoute frontend)
147+ runMyRouteViewT = runRouteViewT ve
150148 runWithHeadAndBody $ \appendHead appendBody -> runMyRouteViewT $ do
151− mapRoutedT (mapEventWriterT appendHead) $ _frontend_head frontend
152− mapRoutedT (mapEventWriterT appendBody) $ _frontend_body frontend
149+ mapRoutedT (mapSetRouteT appendHead) $ _frontend_head frontend
150+ mapRoutedT (mapSetRouteT appendBody) $ _frontend_body frontend
153151
154152renderFrontendHtml
155− :: (Semigroup w, t ~ SpiderTimeline Global)
153+ :: (t ~ SpiderTimeline Global)
156154 => r
157− -> RoutedT t r (EventWriterT t w (PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global))))) ()
158− -> 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))))) ()
156+ -> RoutedT t r (SetRouteT t r' (PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global))))) ()
159157 -> IO ByteString
160158renderFrontendHtml route headWidget bodyWidget = do
161159 --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) $
163161 el "html" $ do
164162 el "head" headWidget
165163 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 @@
11name: obelisk-route
2−version: 0.1
2+version: 0.2
33cabal-version: >= 1.2
44build-type: Simple
55
@@ -11,6 +11,7 @@ library
1111 containers,
1212 dependent-map,
1313 dependent-sum,
14+ dependent-sum-universe-orphans,
1415 dependent-sum-template,
1516 either,
1617 ghcjs-dom,
lib/route/src/Obelisk/Route.hs+404−300
@@ -20,40 +20,50 @@ module Obelisk.Route
2020 ( R
2121 , pattern (:/)
2222 , PageName
23− , Encoder (..) --TODO: unsafe
23+ , PathQuery
24+ , Encoder
25+ , unsafeEncoder
2426 , checkEncoder
25− , ValidEncoder (..) --TODO: unsafe
26− , ValidEncoderFunc (..)
27+ , EncoderImpl (..)
28+ , EncoderFunc (..)
29+ , unsafeMkEncoder
30+ , encode
31+ , decode
32+ , tryDecode
33+ , hoistCheck
34+ , hoistParse
2735 , mapSome
36+ , SegmentResult (..)
2837 , pathComponentEncoder
2938 , enumEncoder
3039 , enum1Encoder
3140 , checkEnum1EncoderFunc
32− , endValidEncoder
33− , pathOnlyValidEncoder
34− , singletonListValidEncoder
41+ , unitEncoder
42+ , pathOnlyEncoder
43+ , singletonListEncoder
3544 , unpackTextEncoder
3645 , prefixTextEncoder
37− , unsafeTshowValidEncoder
38− , someConstValidEncoder
39− , singlePathSegmentValidEncoder
40− , justValidEncoder
41− , nothingValidEncoder
42− , isoValidEncoder
43− , wrappedValidEncoder
44− , unwrappedValidEncoder
46+ , unsafeTshowEncoder
47+ , someConstEncoder
48+ , singlePathSegmentEncoder
49+ , maybeEncoder
50+ , maybeToEitherEncoder
51+ , justEncoder
52+ , nothingEncoder
53+ , isoEncoder
54+ , wrappedEncoder
55+ , unwrappedEncoder
4556 , listToNonEmptyEncoder
4657 , prefixNonemptyTextEncoder
4758 , joinPairTextEncoder
4859 , toListMapEncoder
4960 , shadowEncoder
50− , prismValidEncoder
61+ , prismEncoder
5162 , rPrism
5263 , obeliskRouteEncoder
53− , obeliskRouteComponentEncoder
54− , checkObeliskRouteRestEncoder
55− , pageNameValidEncoder
56− , catchValidEncoder
64+ , obeliskRouteSegment
65+ , pageNameEncoder
66+ , handleEncoder
5767 , ObeliskRoute (..)
5868 , _ObeliskRoute_App
5969 , _ObeliskRoute_Resource
@@ -61,11 +71,13 @@ module Obelisk.Route
6171 , JSaddleWarpRoute (..)
6272 , jsaddleWarpRouteEncoder
6373 , IndexOnlyRoute (..)
64− , indexOnlyRouteComponentEncoder
65− , indexOnlyRouteRestEncoder
74+ , indexOnlyRouteSegment
75+ , indexOnlyRouteEncoder
6676 , someSumEncoder
6777 , Void1
6878 , void1Encoder
79+ , pathSegmentsTextEncoder
80+ , queryParametersTextEncoder
6981 ) where
7082
7183import Prelude hiding ((.), id)
@@ -95,6 +107,7 @@ import Data.Set (Set)
95107import qualified Data.Set as Set
96108import Data.Some (Some)
97109import qualified Data.Some as Some
110+import Data.Some.Universe.Orphans ()
98111import Data.Text (Text)
99112import qualified Data.Text as T
100113import Data.Text.Encoding
@@ -139,41 +152,83 @@ mapSome f (Some.This a) = Some.This $ f a
139152-- Encoder fundamentals
140153--------------------------------------------------------------------------------
141154
142−newtype Encoder check parse decoded encoded = Encoder { unEncoder :: check (ValidEncoder parse decoded encoded) }
143−
144−-- | Law:
145−-- forall p. _validEncoder_decode ve . _validEncoder_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.
147−data ValidEncoder parse decoded encoded = ValidEncoder
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
149− , _validEncoder_encode :: !(decoded -> encoded) -- Must be injective
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.
159+newtype Encoder check parse decoded encoded =
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.
166+-- Law:
167+-- forall p. _encoderImpl_decode ve . _encoderImpl_encode ve p == pure
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.
169+data EncoderImpl parse decoded encoded = EncoderImpl
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
171+ , _encoderImpl_encode :: !(decoded -> encoded) -- Must be injective
150172 }
151173
152−checkEncoder :: Encoder check parse decoded encoded -> check (ValidEncoder parse decoded encoded)
153−checkEncoder = unEncoder
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)
208+checkEncoder = fmap unsafeMkEncoder . unEncoder
154209
155210instance (Applicative check, Monad parse) => Category (Encoder check parse) where
156211 id = Encoder $ pure id
157212 Encoder f . Encoder g = Encoder $ liftA2 (.) f g
158213
159−instance Monad parse => Category (ValidEncoder parse) where
160− id = ValidEncoder
161− { _validEncoder_decode = pure
162− , _validEncoder_encode = id
214+instance Monad parse => Category (EncoderImpl parse) where
215+ id = EncoderImpl
216+ { _encoderImpl_decode = pure
217+ , _encoderImpl_encode = id
163218 }
164− f . g = ValidEncoder
165− { _validEncoder_decode = _validEncoder_decode g <=< _validEncoder_decode f
166− , _validEncoder_encode = _validEncoder_encode f . _validEncoder_encode g
219+ f . g = EncoderImpl
220+ { _encoderImpl_decode = _encoderImpl_decode g <=< _encoderImpl_decode f
221+ , _encoderImpl_encode = _encoderImpl_encode f . _encoderImpl_encode g
167222 }
168223
169−instance Monad parse => PFunctor (,) (ValidEncoder parse) (ValidEncoder parse) where
224+instance Monad parse => PFunctor (,) (EncoderImpl parse) (EncoderImpl parse) where
170225 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
172227 second g = bimap id g
173−instance Monad parse => Bifunctor (,) (ValidEncoder parse) (ValidEncoder parse) (ValidEncoder parse) where
174− bimap f g = ValidEncoder
175− { _validEncoder_encode = bimap (_validEncoder_encode f) (_validEncoder_encode g)
176− , _validEncoder_decode = \(a, b) -> liftA2 (,) (_validEncoder_decode f a) (_validEncoder_decode g b)
228+instance Monad parse => Bifunctor (,) (EncoderImpl parse) (EncoderImpl parse) (EncoderImpl parse) where
229+ bimap f g = EncoderImpl
230+ { _encoderImpl_encode = bimap (_encoderImpl_encode f) (_encoderImpl_encode g)
231+ , _encoderImpl_decode = \(a, b) -> liftA2 (,) (_encoderImpl_decode f a) (_encoderImpl_decode g b)
177232 }
178233
179234instance (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)
181236instance (Applicative check, Monad parse) => QFunctor (,) (Encoder check parse) (Encoder check parse) where
182237 second g = bimap id g
183238instance (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)
185240
186−instance (Traversable f, Monad parse) => Cat.Functor f (ValidEncoder parse) (ValidEncoder parse) where
187− fmap ve = ValidEncoder
188− { _validEncoder_encode = fmap $ _validEncoder_encode ve
189− , _validEncoder_decode = traverse $ _validEncoder_decode ve
241+instance (Traversable f, Monad parse) => Cat.Functor f (EncoderImpl parse) (EncoderImpl parse) where
242+ fmap ve = EncoderImpl
243+ { _encoderImpl_encode = fmap $ _encoderImpl_encode ve
244+ , _encoderImpl_decode = traverse $ _encoderImpl_decode ve
190245 }
191246
192247instance (Traversable f, Monad check, Monad parse) => Cat.Functor f (Encoder check parse) (Encoder check parse) where
193248 fmap e = Encoder $ do
194− ve <- checkEncoder e
249+ ve <- unEncoder e
195250 pure $ Cat.fmap ve
196251
197−instance Monad parse => Associative (ValidEncoder parse) (,) where
198− associate = ValidEncoder
199− { _validEncoder_encode = associate
200− , _validEncoder_decode = pure . disassociate
252+instance Monad parse => Associative (EncoderImpl parse) (,) where
253+ associate = EncoderImpl
254+ { _encoderImpl_encode = associate
255+ , _encoderImpl_decode = pure . disassociate
201256 }
202− disassociate = ValidEncoder
203− { _validEncoder_encode = disassociate
204− , _validEncoder_decode = pure . associate
257+ disassociate = EncoderImpl
258+ { _encoderImpl_encode = disassociate
259+ , _encoderImpl_decode = pure . associate
205260 }
206261
207−instance Monad parse => Monoidal (ValidEncoder parse) (,) where
208− type Id (ValidEncoder parse) (,) = ()
209− idl = ValidEncoder
210− { _validEncoder_encode = idl
211− , _validEncoder_decode = pure . coidl
262+instance Monad parse => Monoidal (EncoderImpl parse) (,) where
263+ type Id (EncoderImpl parse) (,) = ()
264+ idl = EncoderImpl
265+ { _encoderImpl_encode = idl
266+ , _encoderImpl_decode = pure . coidl
212267 }
213− idr = ValidEncoder
214− { _validEncoder_encode = idr
215− , _validEncoder_decode = pure . coidr
268+ idr = EncoderImpl
269+ { _encoderImpl_encode = idr
270+ , _encoderImpl_decode = pure . coidr
216271 }
217− coidl = ValidEncoder
218− { _validEncoder_encode = coidl
219− , _validEncoder_decode = pure . idl
272+ coidl = EncoderImpl
273+ { _encoderImpl_encode = coidl
274+ , _encoderImpl_decode = pure . idl
220275 }
221− coidr = ValidEncoder
222− { _validEncoder_encode = coidr
223− , _validEncoder_decode = pure . idr
276+ coidr = EncoderImpl
277+ { _encoderImpl_encode = coidr
278+ , _encoderImpl_decode = pure . idr
224279 }
225280
226281instance (Applicative check, Monad parse) => Associative (Encoder check parse) (,) where
@@ -234,53 +289,120 @@ instance (Applicative check, Monad parse) => Monoidal (Encoder check parse) (,)
234289 coidl = Encoder $ pure coidl
235290 coidr = Encoder $ pure coidr
236291
237−
238292--------------------------------------------------------------------------------
239293-- Specific instances of encoders
240294--------------------------------------------------------------------------------
241295
242−-- | A path and query string, in which trailing slashes don't matter in the path
243−-- and duplicate query parameters are not allowed
244−type PageName = ([Text], Map Text (Maybe Text))
296+-- | Given a valid 'Iso' from lens, construct an 'Encoder'
297+isoEncoder :: (Applicative check, Applicative parse) => Iso' a b -> Encoder check parse a b
298+isoEncoder f = unsafeMkEncoder $ EncoderImpl
299+ { _encoderImpl_encode = view f
300+ , _encoderImpl_decode = pure . view (from f)
301+ }
245302
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'
247305
248−someConstValidEncoder :: Applicative parse => ValidEncoder parse (Some (Const a)) a
249−someConstValidEncoder = ValidEncoder
250− { _validEncoder_encode = \(Some.This (Const a)) -> a
251− , _validEncoder_decode = pure . Some.This . Const
306+unwrappedEncoder :: (Wrapped a, Applicative check, Applicative parse) => Encoder check parse a (Unwrapped a)
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
340+ { _encoderImpl_encode = \(Some.This (Const a)) -> a
341+ , _encoderImpl_decode = pure . Some.This . Const
252342 }
253343
254344-- | WARNING: This is only safe if the Show and Read instances for 'a' are
255345-- inverses of each other
256−unsafeTshowValidEncoder :: (Show a, Read a, MonadError Text parse) => ValidEncoder parse a Text
257−unsafeTshowValidEncoder = ValidEncoder
258− { _validEncoder_encode = tshow
259− , _validEncoder_decode = \raw -> case readMaybe $ T.unpack raw of
260− Nothing -> throwError $ "unsafeTshowValidEncoder: couldn't decode " <> tshow raw
346+unsafeTshowEncoder :: (Show a, Read a, Applicative check, MonadError Text parse) => Encoder check parse a Text
347+unsafeTshowEncoder = unsafeMkEncoder $ EncoderImpl
348+ { _encoderImpl_encode = tshow
349+ , _encoderImpl_decode = \raw -> case readMaybe $ T.unpack raw of
350+ Nothing -> throwError $ "unsafeTshowEncoderImpl: couldn't decode " <> tshow raw
261351 Just parsed -> pure parsed
262352 }
263353
264−justValidEncoder :: MonadError Text parse => ValidEncoder parse a (Maybe a)
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'
354+newtype EncoderFunc check parse p r = EncoderFunc { runEncoderImplFunc :: forall a. p a -> Encoder check parse a r }
278355
279−unwrappedValidEncoder :: (Wrapped a, Applicative parse) => ValidEncoder parse a (Unwrapped a)
280−unwrappedValidEncoder = isoValidEncoder _Wrapped'
356+newtype Flip f a b = Flip { unFlip :: f b a }
281357
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
283−pathComponentEncoder this rest = chainEncoder (lensEncoder (\(_, b) a -> (a, b)) Prelude.fst consValidEncoder) this rest
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 =
405+ chainEncoder (lensEncoder (\(_, b) a -> (a, b)) Prelude.fst consEncoder) this rest
284406
285407--NOTE: Naming convention in this module is to always talk about things in the *encoding* direction, never in the *decoding* direction
286408
@@ -289,34 +411,40 @@ chainEncoder
289411 ( Monad check
290412 , Monad parse
291413 )
292− => ValidEncoder parse (b, r) r
414+ => Encoder check parse (b, r) r
293415 -> 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)
295417 -> Encoder check parse (R p) r
296418chainEncoder cons this rest = Encoder $ do
297− thisValid <- checkEncoder this
298− pure $ ValidEncoder
299− { _validEncoder_decode = \v -> do
300− (here, following) <- _validEncoder_decode cons v
301− Some.This r <- _validEncoder_decode thisValid here
302− (r :/) <$> _validEncoder_decode (rest r) following
303− , _validEncoder_encode = \(r :/ s) ->
304− _validEncoder_encode cons (_validEncoder_encode thisValid $ Some.This r, _validEncoder_encode (rest r) s)
419+ consValid <- unEncoder cons
420+ thisValid <- unEncoder this
421+ pure $ EncoderImpl
422+ { _encoderImpl_decode = \v -> do
423+ (here, following) <- _encoderImpl_decode consValid v
424+ Some.This r <- _encoderImpl_decode thisValid here
425+ (r :/) <$> _encoderImpl_decode (runIdentity . unEncoder $ rest r) following
426+ , _encoderImpl_encode = \(r :/ s) ->
427+ _encoderImpl_encode consValid
428+ ( _encoderImpl_encode thisValid $ Some.This r
429+ , _encoderImpl_encode (runIdentity . unEncoder $ rest r) s)
305430 }
306431
307432--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
309−lensEncoder set get g = ValidEncoder
310− { _validEncoder_encode = \(ma, b) -> set b $ _validEncoder_encode g (ma, get b)
311− , _validEncoder_decode = \b -> do
312− (ma, la) <- _validEncoder_decode g $ get b
313− pure (ma, set b la)
314− }
433+lensEncoder :: (Applicative check, Monad parse)
434+ => (b -> [a] -> b) -> (b -> [a]) -> Encoder check parse (c, [a]) [a] -> Encoder check parse (c, b) b
435+lensEncoder set get g = Encoder $ do
436+ gImpl <- unEncoder g
437+ pure $ EncoderImpl
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
441+ pure (ma, set b la)
442+ }
315443
316−consValidEncoder :: Applicative parse => ValidEncoder parse (Maybe a, [a]) [a] --TODO: Really shouldn't *always* have the [a], even in the Nothing case
317−consValidEncoder = ValidEncoder
318− { _validEncoder_encode = \(h, t) -> maybeToList h <> t
319− , _validEncoder_decode = pure . \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
445+consEncoder = unsafeMkEncoder $ EncoderImpl
446+ { _encoderImpl_encode = \(h, t) -> maybeToList h <> t
447+ , _encoderImpl_decode = pure . \case
320448 [] -> (Nothing, [])
321449 h:t -> (Just h, t)
322450 }
@@ -336,31 +464,31 @@ shadowEncoder
336464 -> Encoder check parse b c -- ^ Gets overlapped
337465 -> Encoder check parse (Either a b) c
338466shadowEncoder f g = Encoder $ do
339− vf <- checkEncoder f
340− vg <- checkEncoder g
341− let gCanParse c = catchError (Just <$> _validEncoder_decode vg c) (\_ -> pure Nothing)
467+ vf <- unEncoder f
468+ vg <- unEncoder g
469+ let gCanParse c = catchError (Just <$> _encoderImpl_decode vg c) (\_ -> pure Nothing)
342470 overlaps <- fmap catMaybes $ forM universe $ \a -> do
343− let c = _validEncoder_encode vf a
471+ let c = _encoderImpl_encode vf a
344472 mb <- gCanParse c
345473 pure $ fmap (\b -> (a, b, c)) mb
346474 case overlaps of
347475 [] -> pure ()
348476 _ -> throwError $ "shadowEncoder: overlap detected: " <> T.unlines
349477 (flip fmap overlaps $ \(a, b, c) -> "first encoder encodes " <> tshow a <> " as " <> tshow c <> ", which second encoder decodes as " <> tshow b)
350− pure $ ValidEncoder
351− { _validEncoder_encode = \case
352− Left a -> _validEncoder_encode vf a
353− Right b -> _validEncoder_encode vg b
354− , _validEncoder_decode = \c -> (Left <$> _validEncoder_decode vf c) `catchError` \_ -> Right <$> _validEncoder_decode vg c
478+ pure $ EncoderImpl
479+ { _encoderImpl_encode = \case
480+ Left a -> _encoderImpl_encode vf a
481+ Right b -> _encoderImpl_encode vg b
482+ , _encoderImpl_decode = \c -> (Left <$> _encoderImpl_decode vf c) `catchError` \_ -> Right <$> _encoderImpl_decode vg c
355483 }
356484
357485enum1Encoder
358486 :: ( Universe (Some p)
359487 , GShow p
360488 , GCompare p
361− , Ord r
362489 , MonadError Text check
363490 , MonadError Text parse
491+ , Ord r
364492 , Show r
365493 )
366494 => (forall a. p a -> r) -> Encoder check parse (Some p) r
@@ -381,40 +509,40 @@ enumEncoder f = Encoder $ do
381509 case itraverse checkSingleton reversed :: Validation (Map r (Set p)) (Map r p) of
382510 Failure ambiguousEntries -> throwError $ T.unlines $
383511 "enumEncoder: ambiguous encodings detected:" : concat (Map.elems $ imap showRedundant ambiguousEntries)
384− Success m -> pure $ ValidEncoder
385− { _validEncoder_decode = \r -> case Map.lookup r m of
512+ Success m -> pure $ EncoderImpl
513+ { _encoderImpl_decode = \r -> case Map.lookup r m of
386514 Just a -> pure a
387515 Nothing -> throwError $ "enumEncoder: not recognized: " <> tshow r --TODO: Report this as a better type
388− , _validEncoder_encode = f
516+ , _encoderImpl_encode = f
389517 }
390518
391−endValidEncoder :: (MonadError Text parse, Show r, Eq r) => r -> ValidEncoder parse () r
392−endValidEncoder expected = ValidEncoder
393− { _validEncoder_decode = \obtained ->
519+unitEncoder :: (Applicative check, MonadError Text parse, Show r, Eq r) => r -> Encoder check parse () r
520+unitEncoder expected = unsafeMkEncoder $ EncoderImpl
521+ { _encoderImpl_decode = \obtained ->
394522 if obtained == expected
395523 then pure ()
396− else throwError $ "endValidEncoder: expected " <> tshow expected <> ", got " <> tshow obtained
397− , _validEncoder_encode = \_ -> expected
524+ else throwError $ "endEncoderImpl: expected " <> tshow expected <> ", got " <> tshow obtained
525+ , _encoderImpl_encode = \_ -> expected
398526 }
399527
400−singlePathSegmentValidEncoder :: MonadError Text parse => ValidEncoder parse Text PageName
401−singlePathSegmentValidEncoder = pathOnlyValidEncoder . singletonListValidEncoder
528+singlePathSegmentEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse Text PageName
529+singlePathSegmentEncoder = pathOnlyEncoder . singletonListEncoder
402530
403−pathOnlyValidEncoder :: (MonadError Text parse) => ValidEncoder parse [Text] PageName
404−pathOnlyValidEncoder = ValidEncoder
405− { _validEncoder_decode = \(path, query) ->
531+pathOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse [Text] PageName
532+pathOnlyEncoder = unsafeMkEncoder $ EncoderImpl
533+ { _encoderImpl_decode = \(path, query) ->
406534 if query == mempty
407535 then pure path
408− else throwError "pathOnlyValidEncoder: query was provided"
409− , _validEncoder_encode = \path -> (path, mempty)
536+ else throwError "pathOnlyEncoderImpl: query was provided"
537+ , _encoderImpl_encode = \path -> (path, mempty)
410538 }
411539
412−singletonListValidEncoder :: (MonadError Text parse) => ValidEncoder parse a [a]
413−singletonListValidEncoder = ValidEncoder
414− { _validEncoder_decode = \case
540+singletonListEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a [a]
541+singletonListEncoder = unsafeMkEncoder $ EncoderImpl
542+ { _encoderImpl_decode = \case
415543 [a] -> pure a
416− l -> throwError $ "singletonListValidEncoder: expected one item, got " <> tshow (length l)
417− , _validEncoder_encode = (:[])
544+ l -> throwError $ "singletonListEncoderImpl: expected one item, got " <> tshow (length l)
545+ , _encoderImpl_encode = (:[])
418546 }
419547
420548splitTextNonEmpty :: Text -> Text -> NonEmpty Text
@@ -423,18 +551,18 @@ splitTextNonEmpty separator v = case T.splitOn separator v of
423551 h : t -> h :| t
424552
425553--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
427−pathSegmentsTextEncoder = Encoder $ pure $ ValidEncoder
428− { _validEncoder_encode = T.intercalate "/" . fmap (urlEncodeText False) . toList
429− , _validEncoder_decode = pure . fmap (urlDecodeText False) . splitTextNonEmpty "/"
554+pathSegmentsTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse (NonEmpty Text) Text
555+pathSegmentsTextEncoder = unsafeMkEncoder $ EncoderImpl
556+ { _encoderImpl_encode = T.intercalate "/" . fmap (urlEncodeText False) . toList
557+ , _encoderImpl_decode = pure . fmap (urlDecodeText False) . splitTextNonEmpty "/"
430558 }
431559
432560queryParametersTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse [(Text, Maybe Text)] Text
433−queryParametersTextEncoder = Encoder $ pure $ ValidEncoder
434− { _validEncoder_encode = \case
561+queryParametersTextEncoder = Encoder $ pure $ EncoderImpl
562+ { _encoderImpl_encode = \case
435563 [] -> ""
436564 params -> T.intercalate "&" (fmap encodeParameter params)
437− , _validEncoder_decode = pure . \case
565+ , _encoderImpl_decode = pure . \case
438566 "" -> []
439567 encoded ->
440568 let h :| t = splitTextNonEmpty "&" encoded
@@ -456,30 +584,30 @@ urlDecodeText :: Bool -> Text -> Text
456584urlDecodeText q = decodeUtf8 . urlDecode q . encodeUtf8
457585
458586listToNonEmptyEncoder :: (Applicative check, Applicative parse, Monoid a, Eq a) => Encoder check parse [a] (NonEmpty a)
459−listToNonEmptyEncoder = Encoder $ pure $ ValidEncoder
460− { _validEncoder_encode = \case
587+listToNonEmptyEncoder = Encoder $ pure $ EncoderImpl
588+ { _encoderImpl_encode = \case
461589 [] -> mempty :| []
462590 h : t -> h :| t
463− , _validEncoder_decode = \(h :| t) -> pure $
591+ , _encoderImpl_decode = \(h :| t) -> pure $
464592 if h == mempty
465593 then []
466594 else h : t
467595 }
468596
469597prefixTextEncoder :: (Applicative check, MonadError Text parse) => Text -> Encoder check parse Text Text
470−prefixTextEncoder p = Encoder $ pure $ ValidEncoder
471− { _validEncoder_encode = mappend p
472− , _validEncoder_decode = \v -> case T.stripPrefix p v of
598+prefixTextEncoder p = Encoder $ pure $ EncoderImpl
599+ { _encoderImpl_encode = mappend p
600+ , _encoderImpl_decode = \v -> case T.stripPrefix p v of
473601 Nothing -> throwError $ "prefixTextEncoder: wrong prefix; expected " <> tshow p <> ", got " <> tshow (T.take (T.length p) v)
474602 Just stripped -> pure stripped
475603 }
476604
477605prefixNonemptyTextEncoder :: (Applicative check, MonadError Text parse) => Text -> Encoder check parse Text Text
478−prefixNonemptyTextEncoder p = Encoder $ pure $ ValidEncoder
479− { _validEncoder_encode = \case
606+prefixNonemptyTextEncoder p = Encoder $ pure $ EncoderImpl
607+ { _encoderImpl_encode = \case
480608 "" -> ""
481609 v -> p <> v
482− , _validEncoder_decode = \case
610+ , _encoderImpl_decode = \case
483611 "" -> pure ""
484612 v -> case T.stripPrefix p v of
485613 Nothing -> throwError $ "prefixTextEncoder: wrong prefix; expected " <> tshow p
@@ -487,23 +615,23 @@ prefixNonemptyTextEncoder p = Encoder $ pure $ ValidEncoder
487615 }
488616
489617unpackTextEncoder :: (Applicative check, Applicative parse) => Encoder check parse Text String
490−unpackTextEncoder = Encoder $ pure $ ValidEncoder
491− { _validEncoder_encode = T.unpack
492− , _validEncoder_decode = pure . T.pack
618+unpackTextEncoder = Encoder $ pure $ EncoderImpl
619+ { _encoderImpl_encode = T.unpack
620+ , _encoderImpl_decode = pure . T.pack
493621 }
494622
495623toListMapEncoder :: (Applicative check, Applicative parse, Ord k) => Encoder check parse (Map k v) [(k, v)]
496−toListMapEncoder = Encoder $ pure $ ValidEncoder
497− { _validEncoder_encode = Map.toList
498− , _validEncoder_decode = pure . Map.fromList --TODO: Should we be stricter about repeated keys?
624+toListMapEncoder = Encoder $ pure $ EncoderImpl
625+ { _encoderImpl_encode = Map.toList
626+ , _encoderImpl_decode = pure . Map.fromList --TODO: Should we be stricter about repeated keys?
499627 }
500628
501629joinPairTextEncoder :: (MonadError Text check, MonadError Text parse) => Text -> Encoder check parse (Text, Text) Text
502630joinPairTextEncoder = Encoder . \case
503631 "" -> throwError "joinPairTextEncoder: empty separator"
504− separator -> pure $ ValidEncoder
505− { _validEncoder_encode = \(k, v) -> k <> separator <> v
506− , _validEncoder_decode = \r ->
632+ separator -> pure $ EncoderImpl
633+ { _encoderImpl_encode = \(k, v) -> k <> separator <> v
634+ , _encoderImpl_decode = \r ->
507635 let (kt, vt) = T.breakOn separator r
508636 in case vt of
509637 -- The separator was not found
@@ -516,137 +644,123 @@ joinPairTextEncoder = Encoder . \case
516644rPrism :: forall f g. (forall a. Prism' (f a) (g a)) -> Prism' (R f) (R g)
517645rPrism p = prism (\(g :/ x) -> g ^. re p :/ x) (\(f :/ x) -> bimap (:/ x) (:/ x) $ matching p f)
518646
519−-- An encoder that only works on the items available via the prism
520−prismValidEncoder :: MonadError Text parse => Prism' b a -> ValidEncoder parse a b
521−prismValidEncoder p = ValidEncoder
522− { _validEncoder_encode = (^. re p)
523− , _validEncoder_decode = \r -> case r ^? p of
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.
649+prismEncoder :: (Applicative check, MonadError Text parse) => Prism' b a -> Encoder check parse a b
650+prismEncoder p = unsafeMkEncoder $ EncoderImpl
651+ { _encoderImpl_encode = (^. re p)
652+ , _encoderImpl_decode = \r -> case r ^? p of
524653 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"
526655 }
527656
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))
528661
529−-- | Encode a PageName into a path and query string, suitable for use in the
530−-- 'URI' type
531−pageNameValidEncoder :: MonadError Text parse => ValidEncoder parse PageName (String, String)
532−pageNameValidEncoder = ve
533− where Right ve = checkEncoder $ bimap
534− (unpackTextEncoder . prefixTextEncoder "/" . pathSegmentsTextEncoder . listToNonEmptyEncoder)
535− (unpackTextEncoder . prefixNonemptyTextEncoder "?" . queryParametersTextEncoder . toListMapEncoder)
536−
537−catchValidEncoder :: (e -> a) -> ValidEncoder (Either e) a b -> ValidEncoder Identity a b
538−catchValidEncoder recover ve = ve
539− { _validEncoder_decode = \a -> pure $ case _validEncoder_decode ve a of
662+-- | A path (separated by slashes), and a query string.
663+type PathQuery = (String, String)
664+
665+-- | Encode a PageName into a path and query string.
666+pageNameEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse PageName PathQuery
667+pageNameEncoder = bimap
668+ (unpackTextEncoder . prefixTextEncoder "/" . pathSegmentsTextEncoder . listToNonEmptyEncoder)
669+ (unpackTextEncoder . prefixNonemptyTextEncoder "?" . queryParametersTextEncoder . toListMapEncoder)
670+
671+-- | Handle an error in parsing, for example, in order to redirect to a 404 page.
672+handleEncoder
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
540681 Right r -> r
541682 Left err -> recover err
542− }
683+ }
543684
544685--------------------------------------------------------------------------------
545686-- Actual obelisk route info
546687--------------------------------------------------------------------------------
547688
689+-- | A type which can represent Obelisk-specific resource routes, in addition to application specific routes which serve your
690+-- frontend.
548691data ObeliskRoute :: (* -> *) -> * -> * where
549692 -- 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
550693 ObeliskRoute_App :: f a -> ObeliskRoute f a
551694 ObeliskRoute_Resource :: ResourceRoute a -> ObeliskRoute f a
552695
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'
553713data ResourceRoute :: * -> * where
554714 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")
555715 ResourceRoute_Ghcjs :: ResourceRoute [Text]
556716 ResourceRoute_JSaddleWarp :: ResourceRoute (R JSaddleWarpRoute)
557717
558−--TODO: Figure out a way to check this
559−obeliskComponentEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (ObeliskRoute f)) (Either (Some ResourceRoute) (Some f))
560−obeliskComponentEncoder = Encoder $ pure $ ValidEncoder
561− { _validEncoder_encode = \(Some.This o) -> case o of
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.
585− ( Universe (Some appRoute)
586− , GCompare appRoute
718+-- | If there are no additional backend routes in your app (i.e. ObeliskRoute gives you all the routes you need),
719+-- this constructs a suitable 'Encoder' to use for encoding routes to 'PageName's. If you do have additional backend routes,
720+-- you'll want to use 'pathComponentEncoder' yourself, applied to a function that will likely use obeliskRouteSegment in order to
721+-- handle the ObeliskRoute case (i.e. Obelisk resource routes and app frontend routes).
722+obeliskRouteEncoder :: forall check parse appRoute.
723+ ( Universe (Some (ObeliskRoute appRoute))
724+ , GCompare (ObeliskRoute appRoute)
587725 , GShow appRoute
588726 , MonadError Text check
589727 , check ~ parse --TODO: Get rid of this
590728 )
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)
593730 -> Encoder check parse (R (ObeliskRoute appRoute)) PageName
594−obeliskRouteEncoder appComponentEncoder appRouteEncoder = Encoder $ do
595− let componentEncoder = obeliskRouteComponentEncoder appComponentEncoder
596− restEncoder <- checkObeliskRouteRestEncoder appRouteEncoder
597− checkEncoder $ pathComponentEncoder componentEncoder $ runValidEncoderFunc restEncoder
598−
599−checkObeliskRouteRestEncoder
600− :: ( MonadError Text check
601− , MonadError Text parse
602− , Universe (Some appRoute)
603− , GCompare appRoute
604− )
605− => (forall a. appRoute a -> Encoder check parse a PageName)
606− -> check (ValidEncoderFunc parse (ObeliskRoute appRoute) PageName)
607−checkObeliskRouteRestEncoder appRouteEncoder = do
608− appRouteValidEncoder <- checkEnum1EncoderFunc appRouteEncoder
609− resourceRouteValidEncoder <- checkEnum1EncoderFunc resourceRouteEncoder
610− return $ ValidEncoderFunc $ \case
611− ObeliskRoute_App appRoute -> runValidEncoderFunc appRouteValidEncoder appRoute
612− ObeliskRoute_Resource resRoute -> runValidEncoderFunc resourceRouteValidEncoder resRoute
613−
614−obeliskRouteComponentEncoder
615− :: ( MonadError Text parse
616− , GShow appRoute
617− , check ~ parse
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"
625− ResourceRoute_Ghcjs -> Just "ghcjs"
626− ResourceRoute_JSaddleWarp -> Just "jsaddle"
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
731+obeliskRouteEncoder appRouteSegment = pathComponentEncoder $ \r ->
732+ obeliskRouteSegment r appRouteSegment
733+
734+-- | From a function which explains how app-specific frontend routes translate into segments, produce a function which does the
735+-- same for ObeliskRoute. This uses the given function for the 'ObeliskRoute_App' case, and 'resourceRouteSegment' for the
736+-- 'ObeliskRoute_Resource' case.
737+obeliskRouteSegment :: forall check parse appRoute a.
738+ (MonadError Text check, MonadError Text parse)
739+ => ObeliskRoute appRoute a
740+ -> (forall b. appRoute b -> SegmentResult check parse b)
741+ -> SegmentResult check parse a
742+obeliskRouteSegment r appRouteSegment = case r of
743+ ObeliskRoute_App appRoute -> appRouteSegment appRoute
744+ ObeliskRoute_Resource resourceRoute -> resourceRouteSegment resourceRoute
745+
746+-- | A function which gives a sane default for how to encode Obelisk resource routes. It's given in this form, because it will
747+-- be combined with other such segment encoders before 'pathComponentEncoder' turns it into a proper 'Encoder'.
748+resourceRouteSegment :: (MonadError Text check, MonadError Text parse) => ResourceRoute a -> SegmentResult check parse a
749+resourceRouteSegment = \case
750+ ResourceRoute_Static -> PathSegment "static" $ pathOnlyEncoder
751+ ResourceRoute_Ghcjs -> PathSegment "ghcjs" $ pathOnlyEncoder
752+ ResourceRoute_JSaddleWarp -> PathSegment "jsaddle" $ jsaddleWarpRouteEncoder
633753
634754data JSaddleWarpRoute :: * -> * where
635755 JSaddleWarpRoute_JavaScript :: JSaddleWarpRoute ()
636756 JSaddleWarpRoute_WebSocket :: JSaddleWarpRoute ()
637757 JSaddleWarpRoute_Sync :: JSaddleWarpRoute [Text]
638758
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−
645759jsaddleWarpRouteEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (R JSaddleWarpRoute) PageName
646−jsaddleWarpRouteEncoder = pathComponentEncoder jsaddleWarpRouteComponentEncoder $ \case
647− JSaddleWarpRoute_JavaScript -> endValidEncoder mempty
648− JSaddleWarpRoute_WebSocket -> endValidEncoder mempty
649− JSaddleWarpRoute_Sync -> pathOnlyValidEncoder
760+jsaddleWarpRouteEncoder = pathComponentEncoder $ \case
761+ JSaddleWarpRoute_JavaScript -> PathSegment "jsaddle.js" $ unitEncoder mempty
762+ JSaddleWarpRoute_WebSocket -> PathEnd $ unitEncoder mempty
763+ JSaddleWarpRoute_Sync -> PathSegment "sync" $ pathOnlyEncoder
650764
651765instance GShow appRoute => GShow (ObeliskRoute appRoute) where
652766 gshowsPrec prec = \case
@@ -655,32 +769,22 @@ instance GShow appRoute => GShow (ObeliskRoute appRoute) where
655769 ObeliskRoute_Resource appRoute -> showParen (prec > 10) $
656770 showString "ObeliskRoute_Resource " . gshowsPrec 11 appRoute
657771
658−
659772data IndexOnlyRoute :: * -> * where
660773 IndexOnlyRoute :: IndexOnlyRoute ()
661774
662−indexOnlyRouteComponentEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (Some IndexOnlyRoute) (Maybe Text)
663−indexOnlyRouteComponentEncoder = enum1Encoder $ \case
664− IndexOnlyRoute -> Nothing
775+indexOnlyRouteSegment :: (Applicative check, MonadError Text parse) => IndexOnlyRoute a -> SegmentResult check parse a
776+indexOnlyRouteSegment = \case
777+ IndexOnlyRoute -> PathEnd $ unitEncoder mempty
665778
666−indexOnlyRouteRestEncoder :: (Applicative check, MonadError Text parse) => IndexOnlyRoute a -> Encoder check parse a PageName
667−indexOnlyRouteRestEncoder = \case
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− }
779+indexOnlyRouteEncoder :: (MonadError Text check, MonadError Text parse) => Encoder check parse (R IndexOnlyRoute) PageName
780+indexOnlyRouteEncoder = pathComponentEncoder indexOnlyRouteSegment
677781
678782someSumEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (Sum a b)) (Either (Some a) (Some b))
679−someSumEncoder = Encoder $ pure $ ValidEncoder
680− { _validEncoder_encode = \(Some.This t) -> case t of
783+someSumEncoder = Encoder $ pure $ EncoderImpl
784+ { _encoderImpl_encode = \(Some.This t) -> case t of
681785 InL l -> Left $ Some.This l
682786 InR r -> Right $ Some.This r
683− , _validEncoder_decode = pure . \case
787+ , _encoderImpl_decode = pure . \case
684788 Left (Some.This l) -> Some.This (InL l)
685789 Right (Some.This r) -> Some.This (InR r)
686790 }
@@ -691,23 +795,23 @@ instance Universe (Some Void1) where
691795 universe = []
692796
693797void1Encoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Some Void1) a
694−void1Encoder = Encoder $ pure $ ValidEncoder
695− { _validEncoder_encode = \case
798+void1Encoder = Encoder $ pure $ EncoderImpl
799+ { _encoderImpl_encode = \case
696800 Some.This f -> case f of {}
697− , _validEncoder_decode = \_ -> throwError "void1Encoder: can't decode anything"
801+ , _encoderImpl_decode = \_ -> throwError "void1Encoder: can't decode anything"
698802 }
699803
700804instance GShow Void1 where
701805 gshowsPrec _ = \case {}
702806
703−makePrisms ''ObeliskRoute
704−
705807concat <$> mapM deriveRouteComponent
706808 [ ''ResourceRoute
707809 , ''JSaddleWarpRoute
708810 , ''IndexOnlyRoute
709811 ]
710812
813+makePrisms ''ObeliskRoute
814+
711815deriveGEq ''Void1
712816deriveGCompare ''Void1
713817
lib/route/src/Obelisk/Route/Frontend.hs+141−15
@@ -19,6 +19,9 @@
1919{-# LANGUAGE ViewPatterns #-}
2020{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2121{-# LANGUAGE UndecidableInstances #-}
22+{-# LANGUAGE DataKinds #-}
23+{-# LANGUAGE FunctionalDependencies #-}
24+
2225module Obelisk.Route.Frontend
2326 ( module Obelisk.Route
2427 , pattern (:~)
@@ -30,7 +33,14 @@ module Obelisk.Route.Frontend
3033 , mapRoutedT
3134 , subRoute
3235 , subRoute_
36+ , maybeRoute
37+ , maybeRoute_
38+ , maybeRouted
3339 , runRouteViewT
40+ , SetRouteT(..)
41+ , SetRoute(..)
42+ , runSetRouteT
43+ , mapSetRouteT
3444 ) where
3545
3646import Prelude hiding ((.), id)
@@ -51,7 +61,7 @@ import Data.Constraint (Dict (..))
5161import Data.Dependent.Sum (DSum (..))
5262import Data.GADT.Compare
5363import Data.Monoid
54−import Data.Text (Text)
64+import qualified Data.Text as T
5565import Data.Functor.Compose
5666import Reflex.Class
5767import Reflex.Host.Class
@@ -67,6 +77,10 @@ import Language.Javascript.JSaddle --TODO: Get rid of this - other platforms can
6777import Reflex.Dom.Core
6878import qualified GHCJS.DOM.Types as DOM
6979import Network.URI
80+#if defined(ios_HOST_OS)
81+import Data.Maybe (fromMaybe)
82+import qualified Data.List as L
83+#endif
7084
7185import Unsafe.Coerce
7286
@@ -74,7 +88,7 @@ infixr 5 :~
7488pattern (:~) :: Reflex t => f a -> Dynamic t a -> DSum f (Compose (Dynamic t) Identity)
7589pattern a :~ b <- a :=> (coerceDynamic . getCompose -> b)
7690
77−class Routed t r m where
91+class Routed t r m | m -> t r where
7892 askRoute :: m (Dynamic t r)
7993
8094instance 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
153167subRoute f = factorRouted $ strictDynWidget $ \(c :=> r') -> do
154168 runRoutedT (f c) r'
155169
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+
156186dsumValueCoercion :: Coercion f g -> Coercion (DSum k f) (DSum k g)
157187dsumValueCoercion Coercion = Coercion
158188
@@ -164,6 +194,11 @@ factorRouted r = RoutedT $ ReaderT $ \d -> do
164194 d' <- factorDyn d
165195 runRoutedT r $ (coerceWith (dynamicCoercion $ dsumValueCoercion dynamicIdentityCoercion) d')
166196
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+
167202-- | WARNING: The input 'Dynamic' must be fully constructed when this is run
168203strictDynWidget :: (MonadSample t m, MonadHold t m, Adjustable t m) => (a -> m b) -> RoutedT t a m (Dynamic t b)
169204strictDynWidget f = RoutedT $ ReaderT $ \r -> do
@@ -177,6 +212,69 @@ strictDynWidget_ f = RoutedT $ ReaderT $ \r -> do
177212 (_, _) <- runWithReplace (f r0) $ f <$> updated r
178213 pure ()
179214
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+
180278runRouteViewT
181279 :: forall t m r a.
182280 ( TriggerEvent t m
@@ -186,18 +284,24 @@ runRouteViewT
186284 , MonadJSM (Performable m)
187285 , MonadFix m
188286 )
189− => (ValidEncoder (Either Text) r PageName)
190− -> (Text -> r) -- ^ 404 page
191− -> RoutedT t r (EventWriterT t (Endo r) m) a
287+ => (Encoder Identity Identity r PageName)
288+ -> RoutedT t r (SetRouteT t r m) a
192289 -> m a
193−runRouteViewT routeValidEncoder error404 a = do
290+runRouteViewT routeEncoder a = do
194291 rec historyState <- manageHistory $ HistoryCommand_PushState <$> setState
195− let route :: Dynamic t r
196− route = fmap (runIdentity . _validEncoder_decode (catchValidEncoder error404 $ pageNameValidEncoder . routeValidEncoder) . (uriPath &&& uriQuery) . _historyItem_uri) historyState
197− (result, changeState) <- runEventWriterT $ runRoutedT a route
198− let f oldRoute change =
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'.
296+ route :: Dynamic t r
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
301+ (result, changeState) <- runSetRouteT $ runRoutedT a route
302+ let f (currentHistoryState, oldRoute) change =
199303 let newRoute = appEndo change oldRoute
200− (newPath, newQuery) = _validEncoder_encode (pageNameValidEncoder . routeValidEncoder) newRoute
304+ (newPath, newQuery) = encode theEncoder newRoute
201305 in HistoryStateUpdate
202306 { _historyStateUpdate_state = DOM.SerializedScriptValue jsNull
203307 -- We always provide "" as the title. On Firefox, Chrome, and
@@ -211,10 +315,32 @@ runRouteViewT routeValidEncoder error404 a = do
211315 -- we can change this function later to accommodate.
212316 -- See: https://github.com/whatwg/html/issues/2174
213317 , _historyStateUpdate_title = ""
214− , _historyStateUpdate_uri = Just $ nullURI
215− { uriPath = newPath
216− , uriQuery = newQuery
318+ , _historyStateUpdate_uri = Just $ setAdaptedUriPath newPath $ (_historyItem_uri currentHistoryState)
319+ { uriQuery = newQuery
217320 }
218321 }
219− setState = attachWith f (current route) changeState
322+ setState = attachWith f ((,) <$> current historyState <*> current route) changeState
220323 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
2222 , modern-uri
2323 , mtl
2424 , network
25+ , obelisk-asset-serve-snap
2526 , obelisk-backend
2627 , obelisk-executable-config
2728 , obelisk-executable-config-inject
lib/run/src/Obelisk/Run.hs+16−14
@@ -26,7 +26,6 @@ import Control.Lens ((%~), (^?), _Just, _Right)
2626import qualified Data.Attoparsec.ByteString.Char8 as A
2727import Data.ByteString (ByteString)
2828import qualified Data.ByteString.Char8 as BSC
29−import qualified Data.ByteString.Lazy as LBS
3029import qualified Data.ByteString.Lazy.Char8 as BSLC
3130import Data.Dependent.Sum (DSum (..))
3231import Data.Functor.Identity
@@ -37,7 +36,6 @@ import Data.Semigroup ((<>))
3736import Data.Streaming.Network (bindPortTCP)
3837import Data.Text (Text)
3938import qualified Data.Text as T
40−import Data.Text.Encoding
4139import Language.Javascript.JSaddle.Run (syncPoint)
4240import Language.Javascript.JSaddle.WebSockets
4341import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)
@@ -50,26 +48,28 @@ import Network.Wai.Handler.Warp
5048import Network.Wai.Handler.Warp.Internal (settingsHost, settingsPort)
5149import Network.WebSockets (ConnectionOptions)
5250import Network.WebSockets.Connection (defaultConnectionOptions)
51+import qualified Obelisk.Asset.Serve.Snap as Snap
5352import Obelisk.ExecutableConfig (get)
5453import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
5554import Obelisk.Frontend
5655import Obelisk.Route.Frontend
5756import Reflex.Dom.Core
57+import Snap.Core (Snap)
5858import System.Environment
5959import System.IO
6060import System.Process
6161import Text.URI (URI)
6262import qualified Text.URI as URI
6363import Text.URI.Lens
64−import Snap.Core (writeText)
6564import Obelisk.Backend
6665
6766run
6867 :: Int -- ^ Port to run the backend
68+ -> ([Text] -> Snap ()) -- ^ Static asset handler
6969 -> Backend fullRoute frontendRoute -- ^ Backend
7070 -> Frontend (R frontendRoute) -- ^ Frontend
7171 -> IO ()
72−run port backend frontend = do
72+run port serveStaticAsset backend frontend = do
7373 prettifyOutput
7474 let handleBackendErr (e :: IOException) = hPutStrLn stderr $ "backend stopped; make a change to your code to reload - error " <> show e
7575 --TODO: Use Obelisk.Backend.runBackend; this will require separating the checking and running phases
@@ -80,13 +80,16 @@ run port backend frontend = do
8080 _backend_run backend $ \serveRoute -> do
8181 runSnapWithCommandLineArgs $ do
8282 getRouteWith validFullEncoder >>= \case
83− Left e -> writeText e
84− Right r -> case r of
83+ Identity r -> case r of
8584 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
8786 let conf = defRunConfig { _runConfig_redirectPort = port }
8887 runWidget conf frontend validFullEncoder `finally` killThread backendTid
8988
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+
9093getConfigRoute :: IO (Maybe URI)
9194getConfigRoute = get "config/common/route" >>= \case
9295 Just r -> case URI.mkURI $ T.strip r of
@@ -99,7 +102,7 @@ getConfigRoute = get "config/common/route" >>= \case
99102defAppUri :: URI
100103defAppUri = fromMaybe (error "defAppUri") $ URI.mkURI "http://127.0.0.1:8000"
101104
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 ()
103106runWidget conf frontend validFullEncoder = do
104107 uri <- fromMaybe defAppUri <$> getConfigRoute
105108 let port = fromIntegral $ fromMaybe 80 $ uri ^? uriAuthority . _Right . authPort . _Just
@@ -119,7 +122,7 @@ runWidget conf frontend validFullEncoder = do
119122obeliskApp
120123 :: forall route backendRoute. ConnectionOptions
121124 -> Frontend (R route)
122− -> ValidEncoder (Either Text) (R (Sum backendRoute (ObeliskRoute route))) PageName
125+ -> Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
123126 -> URI
124127 -> Application
125128 -> IO Application
@@ -129,15 +132,14 @@ obeliskApp opts frontend validFullEncoder uri backend = do
129132 syncPoint
130133 jsaddlePath <- URI.mkPathPiece "jsaddle"
131134 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
133136 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
135− Left e -> sendResponse $ W.responseLBS H.status404 [("Content-Type", "text/plain")] $ LBS.fromStrict $ encodeUtf8 e
136− Right r -> case r of
137+ return $ \req sendResponse -> case tryDecode validFullEncoder (W.pathInfo req, mempty) of --TODO: Query strings
138+ Identity r -> case r of
137139 InR (ObeliskRoute_Resource ResourceRoute_JSaddleWarp) :=> Identity jsaddleRoute -> case jsaddleRoute of
138140 JSaddleWarpRoute_JavaScript :/ () -> sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] $ jsaddleJs' (Just jsaddleUri) False
139141 _ -> flip jsaddle sendResponse $ req
140− { W.pathInfo = fst $ _validEncoder_encode jsaddleWarpRouteValidEncoder jsaddleRoute
142+ { W.pathInfo = fst $ encode jsaddleWarpRouteValidEncoder jsaddleRoute
141143 }
142144 InR (ObeliskRoute_App appRouteComponent) :=> Identity appRouteRest -> do
143145 html <- renderJsaddleFrontend (appRouteComponent :/ appRouteRest) frontend
lib/selftest/src/Obelisk/SelfTest.hs+24−20
@@ -1,3 +1,4 @@
1+{-# LANGUAGE EmptyCase #-}
12{-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE OverloadedStrings #-}
34{-# LANGUAGE PatternGuards #-}
@@ -14,6 +15,7 @@ import Data.Semigroup (Semigroup, (<>))
1415import qualified Data.Set as Set
1516import Data.String
1617import Data.Text (Text)
18+import Data.Void
1719import qualified Data.Text as T
1820import qualified Data.Text.IO as T
1921import qualified Network.HTTP.Client as HTTP
@@ -26,19 +28,18 @@ import System.Exit (ExitCode (..))
2628import System.Info
2729import System.IO (Handle, hClose)
2830import System.IO.Temp
29−import System.Process (readProcessWithExitCode)
30−import System.Timeout
31+import System.Process (readProcessWithExitCode, CreateProcess(cwd), readCreateProcessWithExitCode, proc)
3132import Test.Hspec
3233import Test.HUnit.Base
3334
34−import Obelisk.CliApp hiding (runCli)
35+import Obelisk.CliApp hiding (runCli, readCreateProcessWithExitCode)
3536import qualified Obelisk.CliApp as CliApp
3637import Obelisk.Run (getConfigRoute)
3738
3839data ObRunState
3940 = ObRunState_Init
4041 | ObRunState_Startup
41− | ObRunState_BackendStarted Text -- Port of backend
42+ | ObRunState_BackendStarted
4243 deriving (Eq, Show)
4344
4445doubleQuotes :: (IsString a, Semigroup a) => a -> a
@@ -53,9 +54,10 @@ commit msg = void $ run "git"
5354 , doubleQuotes msg
5455 ]
5556
56−runCli :: MonadIO m => CliT IO a -> m a
57+-- TODO replace Void and stop using loosely typed synchronous exceptions
58+runCli :: MonadIO m => CliT Void IO a -> m a
5759runCli f = liftIO $ do
58− c <- newCliConfig Notice False False
60+ c <- newCliConfig Notice False False (\case {})
5961 CliApp.runCli c f
6062
6163tshow :: Show a => a -> Text
@@ -119,6 +121,12 @@ main = do
119121 it "works with default impl" $ inTmp $ \_ -> run "ob" ["init"]
120122 it "works with master branch impl" $ inTmp $ \_ -> run "ob" ["init", "--branch", "master"]
121123 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 ()
122130
123131 it "doesn't create anything when given an invalid impl" $ inTmp $ \tmp -> do
124132 void $ errExit False $ run "ob" ["init", "--symlink", "/dev/null"]
@@ -214,12 +222,12 @@ testObRunInDir mdir httpManager = handle_sh (\case ExitSuccess -> pure (); e ->
214222 writefile "config/common/route" . uri =<< liftIO getFreePort -- Use a free port initially
215223 maybe id chdir mdir $ runHandle "ob" ["run"] $ \stdout -> do
216224 freePort <- liftIO getFreePort
217− (_, firstUri) <- handleObRunStdout httpManager stdout
225+ firstUri <- handleObRunStdout httpManager stdout
218226 let newUri = uri freePort
219227 when (firstUri == newUri) $ errorExit $
220228 "Startup URI (" <> firstUri <> ") is the same as test URI (" <> newUri <> ")"
221229 maybe id (\_ -> chdir "..") mdir $ alterRouteTo newUri stdout
222− (_, runningUri) <- handleObRunStdout httpManager stdout
230+ runningUri <- handleObRunStdout httpManager stdout
223231 if runningUri /= newUri
224232 then errorExit $ "Reloading failed: expected " <> newUri <> " but got " <> runningUri
225233 else exit 0
@@ -266,32 +274,28 @@ alterRouteTo uri stdout = do
266274 "Reloading failed: " <> T.pack (show t)
267275
268276-- | 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
270278handleObRunStdout httpManager stdout = flip fix ObRunState_Init $ \loop state -> do
271279 liftIO (T.hGetLine stdout) >>= \t -> case state of
272280 ObRunState_Init
273− | "Running test..." `T.isPrefixOf` t -> loop ObRunState_Startup
281+ | "Running test..." `T.isPrefixOf` t -> loop ObRunState_BackendStarted
274282 ObRunState_Startup
275283 | 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
277285 | not (T.null t) -> errorExit $ "Startup: " <> t -- If theres any other output here, startup failed
278− ObRunState_BackendStarted port
286+ ObRunState_BackendStarted
279287 | Just uri <- "Frontend running on " `T.stripPrefix` t -> do
280− obRunCheck httpManager stdout port uri
281− pure (port, uri)
288+ obRunCheck httpManager stdout uri
289+ pure uri
282290 | not (T.null t) -> errorExit $ "Started: " <> t -- If theres any other output here, startup failed
283291 _ -> loop state
284292
285293-- | Make requests to frontend/backend servers to check they are working properly
286−obRunCheck :: HTTP.Manager -> Handle -> Text -> Text -> Sh ()
287−obRunCheck httpManager stdout backendPort frontendUri = do
294+obRunCheck :: HTTP.Manager -> Handle -> Text -> Sh ()
295+obRunCheck httpManager _stdout frontendUri = do
288296 let req uri = liftIO $ HTTP.parseRequest (T.unpack uri) >>= flip HTTP.httpLbs httpManager
289297 req frontendUri >>= \r -> when (HTTP.responseStatus r /= HTTP.ok200) $ errorExit $
290298 "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)
295299
296300getFreePort :: IO Socket.PortNumber
297301getFreePort = 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
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
1010import Obelisk.Route
1111import Obelisk.Api.Pipeline
1212
13−backend :: Backend Void1 IndexOnlyRoute
13+backend :: Backend BackendRoute FrontendRoute
1414backend = Backend
1515 { _backend_run = \serve -> serve $ const $ return ()
1616 , _backend_routeEncoder = backendRouteEncoder
skeleton/common/src/Common/Api.hs+1−1
@@ -1,4 +1,4 @@
11module Common.Api where
22
33commonStuff :: 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 @@
33{-# LANGUAGE GADTs #-}
44{-# LANGUAGE LambdaCase #-}
55{-# 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 #-}
712
8−import Prelude hiding ((.))
13+module Common.Route where
914
15+{- -- You will probably want these imports for composing Encoders.
16+import Prelude hiding (id, (.))
1017import Control.Category
11−import Control.Monad.Except
12−import Data.Text
18+-}
19+
20+import Data.Text (Text)
21+import Data.Functor.Identity
1322import Data.Functor.Sum
23+
1424import 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.
1536
1637backendRouteEncoder
17− :: forall check parse. (check ~ parse, MonadError Text parse)
18− => Encoder check parse (R (Sum Void1 (ObeliskRoute IndexOnlyRoute))) PageName
19−backendRouteEncoder = Encoder $ do
20− let componentEncoder = shadowEncoder void1Encoder $ obeliskRouteComponentEncoder indexOnlyRouteComponentEncoder
21− restEncoder <- checkObeliskRouteRestEncoder indexOnlyRouteRestEncoder
22− checkEncoder $ pathComponentEncoder (componentEncoder . someSumEncoder) $ \case
23− InL backendRoute -> case backendRoute of {}
24− InR obeliskRoute -> runValidEncoderFunc restEncoder obeliskRoute
38+ :: Encoder (Either Text) Identity (R (Sum BackendRoute (ObeliskRoute FrontendRoute))) PageName
39+backendRouteEncoder = handleEncoder (const (InL BackendRoute_Missing :/ ())) $
40+ pathComponentEncoder $ \case
41+ InL backendRoute -> case backendRoute of
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
1010 , obelisk-frontend
1111 , obelisk-route
1212 , reflex-dom
13− , static
13+ , obelisk-generated-static
1414 , text
1515 exposed-modules:
1616 Frontend
@@ -24,7 +24,7 @@ executable frontend
2424 , obelisk-frontend
2525 , obelisk-route
2626 , reflex-dom
27− , static
27+ , obelisk-generated-static
2828 , frontend
2929 --TODO: Make these ghc-options optional
3030 ghc-options: -threaded
skeleton/frontend/src/Frontend.hs+4−3
@@ -9,14 +9,15 @@ import Obelisk.Route
99import Reflex.Dom.Core
1010
1111import Common.Api
12−import Static
12+import Common.Route
13+import Obelisk.Generated.Static
1314
14−frontend :: Frontend (R IndexOnlyRoute)
15+
16+frontend :: Frontend (R FrontendRoute)
1517frontend = Frontend
1618 { _frontend_head = el "title" $ text "Obelisk Minimal Example"
1719 , _frontend_body = do
1820 text "Welcome to Obelisk!"
1921 el "p" $ text $ T.pack commonStuff
2022 elAttr "img" ("src" =: static @"obelisk.jpg") blank
21− , _frontend_notFoundRoute = const $ IndexOnlyRoute :/ ()
2223 }