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 +4 -4
- data/CHANGELOG.md +13 -1
- data/lib/docyard/cli.rb +17 -0
- data/lib/docyard/deploy/adapters/base.rb +56 -0
- data/lib/docyard/deploy/adapters/cloudflare.rb +38 -0
- data/lib/docyard/deploy/adapters/github_pages.rb +60 -0
- data/lib/docyard/deploy/adapters/netlify.rb +37 -0
- data/lib/docyard/deploy/adapters/vercel.rb +36 -0
- data/lib/docyard/deploy/deployer.rb +95 -0
- data/lib/docyard/deploy/platform_detector.rb +32 -0
- data/lib/docyard/errors.rb +2 -0
- data/lib/docyard/version.rb +1 -1
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4892fa130361d02f347660d81932135f00c71d339c28b292e7bfecbe46ededa0
|
|
4
|
+
data.tar.gz: 4697d95492f675b935db0dad134c11eb0d57f7aca611dd25c3e4e22bded9f031
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
data/lib/docyard/errors.rb
CHANGED
data/lib/docyard/version.rb
CHANGED
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.
|
|
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
|