結局、サイト構築プロセス全体を自動化するために、自分のC#コンソールアプリケーションをロールすることにしました。これらのことはいつもそうであるように、私が望んでいたよりも組み立てるのにはるかに長い時間がかかりましたが、今では1つのコマンドでSubversionから本番環境に直接移動するので、とても満足しています。
まず、素晴らしいMono.Optionsクラスを使用して、コマンドライン引数の取得を処理しました。これは、プロジェクトに追加するだけですぐに使える単一の.csファイルです。
コマンドライン引数が必要だったので、たとえば、デプロイするリビジョンを指定できました(HEADが必要ない場合)。
using Mono.Options;
int rev = 0;
OptionSet opt = new OptionSet();
opt.Add("r=|revison=", "Revision to deploy (defaults to HEAD).", v => rev = int.Parse(v));
オプションをすべて設定したらopt.WriteOptionDescriptions(Console.Out);、使用法のヘルプメッセージを印刷することもできます。
SharpSvnを取得してsvnエクスポートを処理しました。実際には、実装が予想よりもはるかに簡単でした。
using SharpSvn;
SvnClient svn = new SvnClient();
svn.Authentication.DefaultCredentials = new System.Net.NetworkCredential("account", "password");
// Since this is an internal-only tool, I'm not too worried about just
// hardcoding the credentials of an account with read-only access.
SvnExportArgs arg = new SvnExportArgs();
arg.Revision = rev > 0 ? new SvnRevision(rev) : new SvnRevision(SvnRevisionType.Head);
svn.Export(new SvnUriTarget("<repository URL>"), workDir, arg);
...そしてサイト全体が一時フォルダにエクスポートされます(workDir)。svnリビジョンもサイトに印刷したかったので、現在のリポジトリリビジョンを取得しました(リビジョンが指定されていない場合)。
SvnInfoEventArgs ifo;
svn.GetInfo(new SvnUriTarget("<repo URL>"), out ifo);
ifo.Revisionこれで、HEADリビジョンが作成されます。
string.Replace既知のインクルードファイルの小さなセットがあったので、それらを一度メモリにロードし、必要に応じてリビジョン番号をマージしてから、tempフォルダー内の各*.htmlファイルに対して単純化することにしました。
string[] files = Directory.GetFiles(workDir, "*.html", SearchOption.AllDirectories);
foreach (string ff in files)
{
File.Move(ff, workDir + "_.tmp");
using (StreamReader reader = new StreamReader(workDir + "_.tmp"))
{
using (StreamWriter writer = new StreamWriter(ff))
{
string line;
while ((line = reader.ReadLine()) != null)
{
line = line.Replace("<!--#include virtual=\"/top.html\" -->", top);
// <etc..>
writer.WriteLine(line);
}
}
}
File.Delete(workDir + "_.tmp");
}
未処理のファイルを一時的な場所に移動し、元のファイルでを開き、StreamWriter一時ファイルを読み込み、既知のを置き換えて、<!--#include-->一時ファイルを削除します。このプロセスは1秒以内に完了します。
私が行う他のことの1つは、すべてのスクリプトを縮小して、単一の.jsファイルにコンパイルすることです。これにより、開発時に物事を管理しやすくすることができます(クラスは論理的にファイルに編成されます)が、本番用にすべてを最適化します。<script src="...">(タグを20個持つのは非常に悪いので。)
HTML Agility Packは、このタスクに非常に役立ちました。ページテンプレートをにロードし、HtmlDocument縮小して1つのファイルに結合する必要があるスクリプトの場所を抽出しただけです。(スクリプトディレクトリ内の残りの* .jsファイルは特定のページにのみ読み込まれるため、マスターファイルに結合したくありませんでした。)
using HtmlAgilityPack;
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(top);
using (StreamWriter writer = new StreamWriter(workDir + "js\\compiled.js"))
{
foreach (HtmlNode script in doc.DocumentNode.SelectNodes("//script"))
{
string js = script.Attributes["src"].Value;
script.Remove();
js = js.Replace("/js/", workDir + "js/"); // In my site, all scripts are located in the /js folder.
js = js.Replace("/", "\\");
string mini;
if (js.IndexOf(".min.") > 0) // It's already minified.
{
mini = js;
}
else
{
mini = workDir + "_.tmp";
MinifyScript(js, mini);
}
using (StreamReader sr = new StreamReader(mini)) writer.WriteLine(sr.ReadToEnd());
File.Delete(js);
File.Delete(workDir + "_.tmp");
}
}
次に、縮小する残りのスクリプトを探します。
string[] jsfolder = Directory.GetFiles(workDir + "js\\", "*.js");
foreach (string js in jsfolder)
{
if (js.IndexOf("\\compiled.js") > 0) continue; // The compiled js file from above will be in the folder; we want to ignore it.
MinifyScript(js, js);
}
実際の縮小には、JavajarであるYUICompressorを使用しました。ここで選択したコンプレッサーを代用することができます。
static void MinifyScript(string input, string output)
{
System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo(@"C:\Program Files (x86)\Java\jre6\bin\java.exe", "-jar mini.jar -o " + output + " " + input);
si.RedirectStandardOutput = true;
si.UseShellExecute = false;
System.Diagnostics.Process proc = System.Diagnostics.Process.Start(si);
proc.WaitForExit();
if (proc.ExitCode != 0) throw new Exception("Error compiling " + input + ".");
}
私のビルドプロセスでは、最小化ステップは実際にはインクルードの処理の前に行われます(<script>テンプレート内のタグの数を1つに減らしたため)。
最後にMicrosoft.Web.Administration.ServerManager、tempフォルダー内のすべてのファイルを実際のprouctionサイトフォルダーに移動する間、IISを一時的に停止するために使用します。(サイトが半分展開された状態にある間、奇妙なことを防ぎたかったのです。)
using Microsoft.Web.Administration; // Assembly is found in %windir%\System32\inetsrv\Microsoft.Web.Administration.dll
ServerManager iis = new ServerManager();
if (stopIIS) iis.Sites[site].Stop(); // bool stopIIS and string site are set by command line option (and have hardcoded defaults).
string[] files = Directory.GetFiles(workDir, "*");
foreach (string file in files)
{
string name = file.Substring(file.LastIndexOf("\\") + 1);
if (name == "web.config") continue; // The web.config for production is different from that used in development and kept in svn.
try
{
File.Delete(dest + name); // string dest is a command line option (and has a hard-coded default).
}
catch (Exception ex) { }
File.Move(file, dest + name);
}
string[] dirs = Directory.GetDirectories(workDir);
foreach (string dir in dirs)
{
string name = dir.Substring(dir.LastIndexOf("\\") + 1);
if (name == "dyn") continue; // A folder I want to ignore.
try
{
Directory.Delete(dest + name, true);
}
catch (DirectoryNotFoundException ex) { }
Directory.Move(dir, dest + name);
}
if (stopIIS) iis.Sites[site].Start();
これで完了です。ふぅ!
上記のコードから省略した詳細がいくつかあります。たとえば、画像ディレクトリ内のすべての* .psdファイルを削除し、コンパイルされたjsファイルに著作権メッセージを書き込みますが、空白を埋めるのは楽しいことの半分です。 ?
明らかに、ここで紹介したコードの一部は、自分のサイトに対して行った特定の設計上の決定にのみ適用できますが、自動展開を構築する場合は、この一部が役立つことを願っています。プロセス–記録として、これを強くお勧めします。変更をsvnにコミットし、sshを本番サーバーにコミットし、これを実行して実行できるのは非常に便利です。