sakusei 0.5.8 → 0.6.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: 1845947d6ebe2f9b6c5732093eff5bbd90aa7bc2a60ad79a568033bd8c674e57
4
- data.tar.gz: 595a6804e797c8b0c58804b7df122b5a738e5592b786f4a0c29454801c864ed9
3
+ metadata.gz: 51f82cf68852e0f6c9e8ada98c58a804de32dabb7511e5c390c24cbea42e9c4b
4
+ data.tar.gz: 4c4f527519dcd4bba30cd39946c6605e8ea1701594f50e6139509200241cda50
5
5
  SHA512:
6
- metadata.gz: 0c2d9f30c83d0f1de7c73f8c23dcfe590208c057e95cfae0a261a64a36f7a386f3e0e34d9ba1849b46407ce4e19bd86e6dc3544db861fa8ab2f92cf0ac868d6d
7
- data.tar.gz: 29428e2b7e9dbbe86753c58a537b6596f55a7ff7c075efe9bf40c1b0a2ada7d6f9921273400cc8226cf5acb12613a8caffed8ce9efc55576fe9a7d9b7790ca71
6
+ metadata.gz: a6e76264d45720805eb3aaea1b2267bd58e5cb7dd980ee2f53fa6e42dbfffa2cff42c6e6ce9004ab51ea811b139e375511530c6bb8fa63a32fd0101bf83fceb1
7
+ data.tar.gz: f27568e7685b2c0f28fe3938fe1bf865d15a6b5ad250758a659c565b66e8bd538a929ac73fb34caf9ab6e7c9430e0dea7bb50064125c38c223c2e14192e3e07a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sakusei (0.5.8)
4
+ sakusei (0.6.0)
5
5
  erb (~> 4.0)
6
6
  listen (~> 3.8)
7
7
  thor (~> 1.2)
data/bin/sakusei-preview CHANGED
@@ -24,9 +24,9 @@ if args.empty?
24
24
  exit 1
25
25
  end
26
26
 
27
- source_file = args.first
27
+ source_file = Sakusei.resolve_file_extension(args.first)
28
28
  unless File.exist?(source_file)
29
- warn "File not found: #{source_file}"
29
+ warn "File not found: #{args.first}"
30
30
  exit 1
31
31
  end
32
32
 
@@ -8,8 +8,6 @@
8
8
  />
9
9
 
10
10
 
11
- Report Generated <%= today %>
12
-
13
11
  ## Opportunity
14
12
 
15
13
  Meridian Property Group manages a portfolio of 340 residential and commercial tenancies across Southeast Queensland. The business runs on strong fundamentals, but operational bandwidth is a growing constraint: leasing coordinators spend the majority of their week on routine correspondence, manual data entry, and chasing approvals through email chains. This is work that AI and automation can largely eliminate.
Binary file
@@ -53,11 +53,16 @@ module Sakusei
53
53
  style_pack = discover_style_pack
54
54
  $stderr.puts "[sakusei] style pack: #{style_pack.name} (#{style_pack.path})"
55
55
 
56
- $stderr.puts "[sakusei] resolving file includes..."
57
- resolved_content = resolve_files
56
+ if sandboxed?
57
+ $stderr.puts "[sakusei] sandboxed: skipping file includes and ERB"
58
+ processed_content = File.read(@source_file)
59
+ else
60
+ $stderr.puts "[sakusei] resolving file includes..."
61
+ resolved_content = resolve_files
58
62
 
59
- $stderr.puts "[sakusei] processing ERB..."
60
- processed_content = process_erb(resolved_content)
63
+ $stderr.puts "[sakusei] processing ERB..."
64
+ processed_content = process_erb(resolved_content)
65
+ end
61
66
 
62
67
  processed_content = expand_break_syntax(processed_content)
63
68
  processed_content = process_vue(processed_content, style_pack)
@@ -66,6 +71,10 @@ module Sakusei
66
71
  [style_pack, processed_content]
67
72
  end
68
73
 
74
+ def sandboxed?
75
+ !!@options[:sandboxed]
76
+ end
77
+
69
78
  def discover_style_pack
70
79
  StylePack.discover(@options[:source_dir] || @source_dir, @options[:style])
71
80
  end
data/lib/sakusei/cli.rb CHANGED
@@ -24,6 +24,7 @@ module Sakusei
24
24
  option :stylesheet, aliases: '-css', desc: 'Path to CSS stylesheet'
25
25
  option :page_breaks, aliases: '-p', type: :boolean, default: false, desc: 'Add page breaks between files'
26
26
  option :open, type: :boolean, default: false, desc: 'Open the PDF after building'
27
+ option :sandboxed, type: :boolean, default: false, desc: 'Treat input as inert markdown: no ERB, no @include, images restricted to the source directory'
27
28
  def build(*files)
28
29
  raise Error, 'No input files provided' if files.empty?
29
30
 
@@ -242,26 +243,14 @@ module Sakusei
242
243
  return false if arg.nil? || arg.empty?
243
244
  return false if File.extname(arg).length > 0 # Already has an extension
244
245
 
245
- %w[.md .text .markdown].any? { |ext| File.exist?(arg + ext) }
246
+ Sakusei::MARKDOWN_EXTENSIONS.any? { |ext| File.exist?(arg + ext) }
246
247
  end
247
248
 
248
249
  private
249
250
 
250
251
  # Resolve file by trying markdown extensions if no extension provided
251
252
  def resolve_file_extension(file)
252
- return file if File.exist?(file)
253
- return file if File.directory?(file)
254
- return file if file.include?('*') # Glob pattern
255
- return file if File.extname(file).length > 0 # Already has extension
256
-
257
- # Try markdown extensions
258
- %w[.md .text .markdown].each do |ext|
259
- path_with_ext = file + ext
260
- return path_with_ext if File.exist?(path_with_ext)
261
- end
262
-
263
- # Return original if no extension found
264
- file
253
+ Sakusei.resolve_file_extension(file)
265
254
  end
266
255
 
267
256
  def open_pdf(path)
@@ -18,6 +18,9 @@ module Sakusei
18
18
 
19
19
  image_paths.each do |rel_path|
20
20
  src = File.expand_path(rel_path, @source_dir)
21
+ # Sandboxed builds must not read files outside the source directory
22
+ # (image_paths already excludes absolute paths; this blocks ../ escapes).
23
+ next if sandboxed? && !src.start_with?(File.expand_path(@source_dir) + File::SEPARATOR)
21
24
  next unless File.exist?(src)
22
25
 
23
26
  dest = File.join(temp_dir, rel_path)
@@ -26,6 +29,10 @@ module Sakusei
26
29
  end
27
30
  end
28
31
 
32
+ def sandboxed?
33
+ !!@options[:sandboxed]
34
+ end
35
+
29
36
  def image_paths
30
37
  paths = []
31
38
  @content.scan(/!\[[^\]]*\]\(([^)]+)\)/) { |m| paths << m[0].strip }
@@ -60,11 +60,12 @@ module Sakusei
60
60
  parts = []
61
61
 
62
62
  @files.each do |file|
63
- content = File.read(file)
64
-
65
- # Resolve includes within each file
66
- resolver = FileResolver.new(file)
67
- resolved = resolver.resolve
63
+ resolved = if @options[:sandboxed]
64
+ File.read(file)
65
+ else
66
+ # Resolve includes within each file
67
+ FileResolver.new(file).resolve
68
+ end
68
69
 
69
70
  parts << resolved
70
71
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sakusei
4
- VERSION = '0.5.8'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -164,7 +164,7 @@ module Sakusei
164
164
  end
165
165
 
166
166
  def vue_renderer_script
167
- File.expand_path('../vue_renderer.js', __FILE__)
167
+ File.expand_path('../vue_renderer.cjs', __FILE__)
168
168
  end
169
169
 
170
170
  def parse_attributes(attrs_string)
data/lib/sakusei.rb CHANGED
@@ -21,6 +21,28 @@ require_relative 'sakusei/page_chrome_translator'
21
21
  module Sakusei
22
22
  class Error < StandardError; end
23
23
 
24
+ # Markdown extensions to auto-discover when a CLI is given a file argument
25
+ # without an extension (e.g. `sakusei-preview ai_workflow_assessment`).
26
+ MARKDOWN_EXTENSIONS = %w[.md .text .markdown].freeze
27
+
28
+ # Resolve a file argument by trying known markdown extensions if no extension
29
+ # was given. Returns the input unchanged if the file already exists, is a
30
+ # directory, looks like a glob, or already has an extension.
31
+ def self.resolve_file_extension(file)
32
+ return file if file.nil? || file.empty?
33
+ return file if File.exist?(file)
34
+ return file if File.directory?(file)
35
+ return file if file.include?('*')
36
+ return file if File.extname(file).length.positive?
37
+
38
+ MARKDOWN_EXTENSIONS.each do |ext|
39
+ candidate = file + ext
40
+ return candidate if File.exist?(candidate)
41
+ end
42
+
43
+ file
44
+ end
45
+
24
46
  # Main entry point for building PDFs
25
47
  def self.build(source_file, options = {})
26
48
  Builder.new(source_file, options).build
data/sakusei.gemspec CHANGED
@@ -14,7 +14,14 @@ Gem::Specification.new do |spec|
14
14
  spec.required_ruby_version = '>= 3.0.0'
15
15
 
16
16
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
17
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
17
+ if File.directory?('.git') && system('git --version', out: File::NULL, err: File::NULL)
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
19
+ else
20
+ # No git (e.g. vendored via `git archive` inside a Docker image) —
21
+ # glob the shipped tree instead.
22
+ Dir.glob('{lib,bin,examples}/**/*', File::FNM_DOTMATCH).select { |f| File.file?(f) } +
23
+ Dir.glob('*').select { |f| File.file?(f) }
24
+ end
18
25
  end
19
26
  spec.bindir = 'bin'
20
27
  spec.executables = %w[sakusei sakusei-preview]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sakusei
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Rowell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-04 00:00:00.000000000 Z
11
+ date: 2026-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -137,7 +137,7 @@ files:
137
137
  - lib/sakusei/style_preview.rb
138
138
  - lib/sakusei/version.rb
139
139
  - lib/sakusei/vue_processor.rb
140
- - lib/sakusei/vue_renderer.js
140
+ - lib/sakusei/vue_renderer.cjs
141
141
  - lib/templates/base.css
142
142
  - lib/templates/default_style_pack/.gitignore
143
143
  - lib/templates/default_style_pack/components/HelloWorld.vue
File without changes