最后,我决定推出自己的C控制台应用程序来自动化整个站点构建过程。正如这些事情总是发生的那样,整合起来要比我希望的时间长得多,但是现在有一个命令直接把我的站点从颠覆变成了生产,所以我很高兴。
首先,我用了
Mono.Options
类来处理抓取命令行参数。它是
a single .cs file
你可以添加到你的项目中去,并且很好的去做。
我需要命令行参数,例如,我可以指定要部署的修订版(如果我不需要头部的话)。
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
将进行头部修正。
因为我有一组已知的include文件,所以我决定只将它们加载到内存中一次,在需要时合并到修订号中,然后做一个简单的
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-->
然后删除临时文件。这个过程在一秒钟内完成。
我做的另一件事是缩小所有脚本并将它们编译成一个.js文件。这允许我在开发中保持事情的可管理性(类在逻辑上组织成文件),但是为生产优化一切。(因为有二十个
<script src="...">
标签是
Very Bad
)
这个
HTML Agility Pack
对这项任务非常有用。我只是将页面模板加载到
HtmlDocument
并将需要缩小的脚本位置提取出来,合并成一个单独的文件。(脚本目录中其余的*.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);
}
对于实际的缩小,我只是使用
YUI Compressor
,这是一个Java jar。你可以在这里替换你选择的压缩机。
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 + ".");
}
在我的构建过程中,缩小步骤实际上发生在处理include之前(因为我减少了
<script>
标记到模板中的一个)。
最后,我用
Microsoft.Web.Administration.ServerManager
在我将temp文件夹中的所有文件移动到实际的production站点文件夹时临时停止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到我的生产服务器,运行这个,然后完成。