docyard 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe9582d35d963462ff818674735f3b131607368f6adc6fdb70eac6152365efe8
4
- data.tar.gz: a8e8b424702b4ca3ff2726a938ea0696f0b15bed3d82f449f803f64923bbbafc
3
+ metadata.gz: 4892fa130361d02f347660d81932135f00c71d339c28b292e7bfecbe46ededa0
4
+ data.tar.gz: 4697d95492f675b935db0dad134c11eb0d57f7aca611dd25c3e4e22bded9f031
5
5
  SHA512:
6
- metadata.gz: 4c6ae9f9e88f186d43aa13ef62c2c96d3c4ed65e0320558413df73915bd33478b673dae25dc4f3e3fdf6b0fe627ca595a8fc53cded6186ed3afb03ae08ef7f5d
7
- data.tar.gz: 17ad431713b55f78b87a67b5de17db1e58c949895a08ff9d31b6f658aa277a8df8cab84f3d3ee178f9c70a9eb30557089fc0b732e2793b6c9b3c8054bf513b1c
6
+ metadata.gz: 342e2e7f16c32da9da273b6325532ad8ee842d58304faf309a0e634be5f7c2d3a6a8c5eb0176b09e0b518a67150c25223b5ad3be9c67da90e974e4be9871e28e
7
+ data.tar.gz: 2f1b65cde20d044348908884fbfd0a185d47d6ef73d9fdd516e5f11792262700550bb70d6ec7523dc910dcaed864d2df51bf6f54480997092c63dfe926b6629d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.0] - 2026-02-17
11
+
12
+ ### Added
13
+ - **Deploy Command** - One-command deployment with `docyard deploy` supporting Vercel, Netlify, Cloudflare Pages, and GitHub Pages (#153)
14
+ - **Platform Auto-Detection** - Automatically detects deployment platform from project config files (e.g. `vercel.json`, `netlify.toml`)
15
+
16
+ ### Documentation
17
+ - Added Deploy Command page with per-platform setup instructions
18
+ - Updated CLI reference with `docyard deploy` options
19
+ - Cross-linked existing GitHub Pages, Vercel, and Netlify docs to deploy command
20
+
10
21
  ## [1.2.0] - 2026-02-03
11
22
 
12
23
  ### Added
@@ -264,7 +275,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
264
275
  - Initial gem structure
265
276
  - Project scaffolding
266
277
 
267
- [Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.2.0...HEAD
278
+ [Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.3.0...HEAD
279
+ [1.3.0]: https://github.com/sanifhimani/docyard/compare/v1.2.0...v1.3.0
268
280
  [1.2.0]: https://github.com/sanifhimani/docyard/compare/v1.1.0...v1.2.0
269
281
  [1.1.0]: https://github.com/sanifhimani/docyard/compare/v1.0.2...v1.1.0
270
282
  [1.0.2]: https://github.com/sanifhimani/docyard/compare/v1.0.1...v1.0.2
data/lib/docyard/cli.rb CHANGED
@@ -80,6 +80,23 @@ module Docyard
80
80
  exit(doctor.run)
81
81
  end
82
82
 
83
+ desc "deploy", "Deploy the built site to a hosting platform"
84
+ method_option :to, type: :string, desc: "Target platform (vercel, netlify, cloudflare, github-pages)"
85
+ method_option :prod, type: :boolean, default: true, desc: "Deploy to production"
86
+ method_option :skip_build, type: :boolean, default: false, desc: "Skip building before deploy"
87
+ def deploy
88
+ apply_global_options
89
+ require_relative "deploy/deployer"
90
+ deployer = Deploy::Deployer.new(
91
+ to: options[:to],
92
+ production: options[:prod],
93
+ skip_build: options[:skip_build]
94
+ )
95
+ exit(1) unless deployer.deploy
96
+ rescue ConfigError => e
97
+ print_config_error(e)
98
+ end
99
+
83
100
  desc "customize", "Generate theme customization files"
84
101
  method_option :minimal, type: :boolean, default: false, aliases: "-m",
85
102
  desc: "Generate minimal files without comments"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Docyard
6
+ module Deploy
7
+ module Adapters
8
+ class Base
9
+ attr_reader :output_dir, :production, :config
10
+
11
+ def initialize(output_dir:, production:, config:)
12
+ @output_dir = output_dir
13
+ @production = production
14
+ @config = config
15
+ end
16
+
17
+ def deploy
18
+ check_cli_installed!
19
+ run_deploy
20
+ end
21
+
22
+ def platform_name
23
+ raise NotImplementedError
24
+ end
25
+
26
+ private
27
+
28
+ def cli_name
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def cli_install_hint
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def run_deploy
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def check_cli_installed!
41
+ _, _, status = Open3.capture3("which", cli_name)
42
+ return if status.success?
43
+
44
+ raise DeployError, "'#{cli_name}' CLI not found. Install it with: #{cli_install_hint}"
45
+ end
46
+
47
+ def execute_command(*)
48
+ stdout, stderr, status = Open3.capture3(*)
49
+ return stdout if status.success?
50
+
51
+ raise DeployError, "Deploy command failed: #{stderr.strip.empty? ? stdout.strip : stderr.strip}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Docyard
6
+ module Deploy
7
+ module Adapters
8
+ class Cloudflare < Base
9
+ def platform_name
10
+ "Cloudflare Pages"
11
+ end
12
+
13
+ private
14
+
15
+ def cli_name
16
+ "wrangler"
17
+ end
18
+
19
+ def cli_install_hint
20
+ "npm i -g wrangler"
21
+ end
22
+
23
+ def run_deploy
24
+ output = execute_command("wrangler", "pages", "deploy", output_dir, "--project-name=#{project_name}")
25
+ extract_url(output)
26
+ end
27
+
28
+ def project_name
29
+ config.title.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")
30
+ end
31
+
32
+ def extract_url(output)
33
+ output.match(%r{https://\S+\.pages\.dev\S*})&.to_s
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "fileutils"
5
+ require_relative "base"
6
+
7
+ module Docyard
8
+ module Deploy
9
+ module Adapters
10
+ class GithubPages < Base
11
+ def platform_name
12
+ "GitHub Pages"
13
+ end
14
+
15
+ private
16
+
17
+ def cli_name
18
+ "gh"
19
+ end
20
+
21
+ def cli_install_hint
22
+ "https://cli.github.com"
23
+ end
24
+
25
+ def run_deploy
26
+ remote_url = fetch_remote_url
27
+ Dir.mktmpdir do |tmp|
28
+ prepare_deploy_dir(tmp)
29
+ push_to_gh_pages(tmp, remote_url)
30
+ end
31
+ build_pages_url(remote_url)
32
+ end
33
+
34
+ def fetch_remote_url
35
+ execute_command("git", "remote", "get-url", "origin").strip
36
+ end
37
+
38
+ def prepare_deploy_dir(tmp)
39
+ FileUtils.cp_r("#{output_dir}/.", tmp)
40
+ execute_command("git", "-C", tmp, "init", "-b", "gh-pages")
41
+ execute_command("git", "-C", tmp, "add", ".")
42
+ execute_command("git", "-C", tmp, "commit", "-m", "Deploy via docyard")
43
+ end
44
+
45
+ def push_to_gh_pages(tmp, remote_url)
46
+ execute_command("git", "-C", tmp, "remote", "add", "origin", remote_url)
47
+ execute_command("git", "-C", tmp, "push", "--force", "origin", "gh-pages")
48
+ end
49
+
50
+ def build_pages_url(remote_url)
51
+ match = remote_url.match(%r{github\.com[:/]([^/]+)/([^/.]+)})
52
+ return nil unless match
53
+
54
+ owner, repo = match.captures
55
+ "https://#{owner}.github.io/#{repo}/"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Docyard
6
+ module Deploy
7
+ module Adapters
8
+ class Netlify < Base
9
+ def platform_name
10
+ "Netlify"
11
+ end
12
+
13
+ private
14
+
15
+ def cli_name
16
+ "netlify"
17
+ end
18
+
19
+ def cli_install_hint
20
+ "npm i -g netlify-cli"
21
+ end
22
+
23
+ def run_deploy
24
+ args = ["netlify", "deploy", "--dir=#{output_dir}"]
25
+ args << "--prod" if production
26
+ output = execute_command(*args)
27
+ extract_url(output)
28
+ end
29
+
30
+ def extract_url(output)
31
+ pattern = production ? /Website URL:\s+(\S+)/ : /Website draft URL:\s+(\S+)/
32
+ output.match(pattern)&.captures&.first
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Docyard
6
+ module Deploy
7
+ module Adapters
8
+ class Vercel < Base
9
+ def platform_name
10
+ "Vercel"
11
+ end
12
+
13
+ private
14
+
15
+ def cli_name
16
+ "vercel"
17
+ end
18
+
19
+ def cli_install_hint
20
+ "npm i -g vercel"
21
+ end
22
+
23
+ def run_deploy
24
+ args = ["vercel", output_dir, "--yes"]
25
+ args << "--prod" if production
26
+ output = execute_command(*args)
27
+ extract_url(output)
28
+ end
29
+
30
+ def extract_url(output)
31
+ output.strip.lines.last&.strip
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "platform_detector"
4
+ require_relative "adapters/vercel"
5
+ require_relative "adapters/netlify"
6
+ require_relative "adapters/cloudflare"
7
+ require_relative "adapters/github_pages"
8
+
9
+ module Docyard
10
+ module Deploy
11
+ class Deployer
12
+ ADAPTERS = {
13
+ "vercel" => Adapters::Vercel,
14
+ "netlify" => Adapters::Netlify,
15
+ "cloudflare" => Adapters::Cloudflare,
16
+ "github-pages" => Adapters::GithubPages
17
+ }.freeze
18
+
19
+ attr_reader :config, :platform, :production, :skip_build
20
+
21
+ def initialize(to: nil, production: true, skip_build: false)
22
+ @config = Config.new
23
+ @platform = to
24
+ @production = production
25
+ @skip_build = skip_build
26
+ end
27
+
28
+ def deploy
29
+ print_header
30
+ ensure_build
31
+ adapter = resolve_adapter
32
+ print_deploy_info(adapter)
33
+ url = adapter.deploy
34
+ print_success(url)
35
+ true
36
+ rescue DeployError => e
37
+ print_error(e)
38
+ false
39
+ end
40
+
41
+ private
42
+
43
+ def print_header
44
+ puts
45
+ puts " #{UI.bold('Docyard')} v#{VERSION}"
46
+ puts " Deploying..."
47
+ puts
48
+ end
49
+
50
+ def ensure_build
51
+ return if skip_build
52
+
53
+ require_relative "../builder"
54
+ builder = Builder.new
55
+ raise DeployError, "Build failed" unless builder.build
56
+ end
57
+
58
+ def resolve_adapter
59
+ name = platform || detect_platform
60
+ adapter_class = ADAPTERS[name]
61
+ raise DeployError, "Unknown platform: #{name}. Valid options: #{ADAPTERS.keys.join(', ')}" unless adapter_class
62
+
63
+ adapter_class.new(output_dir: config.build.output, production: production, config: config)
64
+ end
65
+
66
+ def detect_platform
67
+ detected = PlatformDetector.new.detect
68
+ return detected if detected
69
+
70
+ raise DeployError,
71
+ "Could not detect platform. Use --to to specify one: #{ADAPTERS.keys.join(', ')}"
72
+ end
73
+
74
+ def print_deploy_info(adapter)
75
+ environment = production ? "production" : "preview"
76
+ puts " #{UI.dim('Platform')} #{adapter.platform_name}"
77
+ puts " #{UI.dim('Environment')} #{environment}"
78
+ puts " #{UI.dim('Directory')} #{config.build.output}/"
79
+ puts
80
+ end
81
+
82
+ def print_success(url)
83
+ puts " #{UI.success('Deployed successfully')}"
84
+ puts " #{url}" if url
85
+ puts
86
+ end
87
+
88
+ def print_error(error)
89
+ puts " #{UI.error('Deploy failed')}"
90
+ puts " #{error.message}"
91
+ puts
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Deploy
5
+ class PlatformDetector
6
+ DETECTION_RULES = [
7
+ { files: ["vercel.json"], dirs: [".vercel"], platform: "vercel" },
8
+ { files: ["netlify.toml"], dirs: [".netlify"], platform: "netlify" },
9
+ { files: ["wrangler.toml", "wrangler.jsonc"], dirs: [], platform: "cloudflare" },
10
+ { files: [], dirs: [".github/workflows"], platform: "github-pages" }
11
+ ].freeze
12
+
13
+ def initialize(project_root = Dir.pwd)
14
+ @project_root = project_root
15
+ end
16
+
17
+ def detect
18
+ DETECTION_RULES.each do |rule|
19
+ return rule[:platform] if matches?(rule)
20
+ end
21
+ nil
22
+ end
23
+
24
+ private
25
+
26
+ def matches?(rule)
27
+ rule[:files].any? { |f| File.exist?(File.join(@project_root, f)) } ||
28
+ rule[:dirs].any? { |d| Dir.exist?(File.join(@project_root, d)) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,4 +8,6 @@ module Docyard
8
8
  class SidebarConfigError < Error; end
9
9
 
10
10
  class BuildError < Error; end
11
+
12
+ class DeployError < Error; end
11
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docyard
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docyard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sanif Himani
@@ -245,6 +245,13 @@ files:
245
245
  - lib/docyard/config/validator.rb
246
246
  - lib/docyard/constants.rb
247
247
  - lib/docyard/customizer.rb
248
+ - lib/docyard/deploy/adapters/base.rb
249
+ - lib/docyard/deploy/adapters/cloudflare.rb
250
+ - lib/docyard/deploy/adapters/github_pages.rb
251
+ - lib/docyard/deploy/adapters/netlify.rb
252
+ - lib/docyard/deploy/adapters/vercel.rb
253
+ - lib/docyard/deploy/deployer.rb
254
+ - lib/docyard/deploy/platform_detector.rb
248
255
  - lib/docyard/diagnostic.rb
249
256
  - lib/docyard/diagnostic_context.rb
250
257
  - lib/docyard/doctor.rb