stipa 0.1.0 → 0.1.2

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: ff378718ea5094cf20614ab1fa80b2ab2fe66b96dbedfc448f230a752034d69d
4
- data.tar.gz: db686eb560e89166d73ddf100c6b65695cc288a0f8118e9461edcb0d9b4e8aa8
3
+ metadata.gz: c22dd3975ab3276a80054b24f2b9e04912139c30e57d2500ffbc79bdf9de37da
4
+ data.tar.gz: 8013ed53bddd21485caff447749c2c36fcdba2344930e283dc70003097b40337
5
5
  SHA512:
6
- metadata.gz: bdc3509de75f0de34b898e68ecbd2f3803eaca1959f0c27162c77b2845e8288bf51f0c329be6db0278491a2a5b9c49010952a11bc804fdc79856250b98594c6f
7
- data.tar.gz: '072216312185f44a7f236477fd06cfe1a52f2a33fc286ddd6d00f4bfec92f21054345222fa9a5d6b0e2141ffb5f28ff441999e44895debcf42d9d06c04f2cf3c'
6
+ metadata.gz: a4f9cb7efbc203c13bd8fb2a0071761b3fa28c22b63d843a40eff56a6234a30a5ba342b29da7145f3af46ff6a42ce11e4ba3fa958083e09d1a99873fba19f736
7
+ data.tar.gz: cae5c45e818554b9732bf6668feff3b932cf9c0cacc107b5678e5a789debe4b89eea691c0f85514be026ae566801fe0159a70f457827722fdb1a0009097e37dc
data/lib/js/stipa-vue.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Stīpa Vue Bootstrapper
2
+ * Stīpa Vue Bootstrapper (ES Module)
3
3
  *
4
4
  * Automatically mounts Vue 3 components declared with the ERB helper:
5
5
  * <%= vue_component("Counter", props: { initial: 5 }) %>
@@ -7,102 +7,82 @@
7
7
  * Which renders on the page as:
8
8
  * <div data-vue-component="Counter" data-props='{"initial":5}'></div>
9
9
  *
10
- * Usage in your layout (after vue_script and component <script> tags):
11
- * <%= stipa_vue_bootstrap %>
10
+ * Usage in your layout:
11
+ * <script type="importmap">{ "imports": { "vue": "/vendor/vue.esm-browser.prod.js" } }</script>
12
+ * <script type="module" src="/stipa-vue.js"></script>
13
+ * <script type="module" src="/app.js"></script>
12
14
  *
13
- * Register components before DOMContentLoaded fires, or call StipaVue.mount()
14
- * manually after dynamic content is inserted.
15
- *
16
- * Example:
17
- * <script type="module">
18
- * import Counter from '/components/Counter.js'
19
- * StipaVue.register('Counter', Counter)
20
- * </script>
21
- * <%= stipa_vue_bootstrap %>
15
+ * In app/main.ts, register components and call mount():
16
+ * const { StipaVue } = window
17
+ * StipaVue.register('Counter', Counter)
18
+ * StipaVue.mount()
22
19
  */
23
- (() => {
24
- const COMPONENT_ATTR = "data-vue-component";
25
- const PROPS_ATTR = "data-props";
26
- const MOUNTED_ATTR = "data-stipa-vue-mounted";
20
+ import { createApp } from 'vue'
27
21
 
28
- const registry = {};
29
- const mounted = [];
22
+ const COMPONENT_ATTR = 'data-vue-component'
23
+ const PROPS_ATTR = 'data-props'
24
+ const MOUNTED_ATTR = 'data-stipa-vue-mounted'
30
25
 
31
- const StipaVue = {
32
- register(name, component) {
33
- registry[name] = component;
34
- },
26
+ const registry = {}
27
+ const mounted = []
35
28
 
36
- mount(root) {
37
- root = root || document;
29
+ const StipaVue = {
30
+ register(name, component) {
31
+ registry[name] = component
32
+ },
38
33
 
39
- if (typeof Vue === "undefined") {
40
- console.error(
41
- "[StipaVue] Vue is not defined. Make sure vue_script() appears before stipa_vue_bootstrap() in your layout.",
42
- );
43
- return;
44
- }
34
+ mount(root) {
35
+ root = root || document
45
36
 
46
- // Prune stale entries for elements no longer in the DOM
47
- for (let i = mounted.length - 1; i >= 0; i--) {
48
- if (!document.contains(mounted[i].el)) {
49
- mounted.splice(i, 1);
50
- }
37
+ // Prune stale entries for elements no longer in the DOM
38
+ for (let i = mounted.length - 1; i >= 0; i--) {
39
+ if (!document.contains(mounted[i].el)) {
40
+ mounted.splice(i, 1)
51
41
  }
42
+ }
52
43
 
53
- const selector = "[" + COMPONENT_ATTR + "]:not([" + MOUNTED_ATTR + "])";
54
- const elements = root.querySelectorAll(selector);
44
+ const selector = `[${COMPONENT_ATTR}]:not([${MOUNTED_ATTR}])`
45
+ const elements = root.querySelectorAll(selector)
55
46
 
56
- elements.forEach((el) => {
57
- const name = el.getAttribute(COMPONENT_ATTR);
58
- const component = registry[name];
47
+ elements.forEach((el) => {
48
+ const name = el.getAttribute(COMPONENT_ATTR)
49
+ const component = registry[name]
59
50
 
60
- if (!component) {
61
- console.warn(
62
- '[StipaVue] Component "' +
63
- name +
64
- '" is not registered. ' +
65
- 'Call StipaVue.register("' +
66
- name +
67
- '", YourComponent) before the DOM loads.',
68
- );
69
- return;
70
- }
51
+ if (!component) {
52
+ console.warn(
53
+ `[StipaVue] Component "${name}" is not registered. ` +
54
+ `Call StipaVue.register("${name}", YourComponent) before calling StipaVue.mount().`
55
+ )
56
+ return
57
+ }
71
58
 
72
- let props = {};
73
- const propsRaw = el.getAttribute(PROPS_ATTR);
74
- if (propsRaw) {
75
- try {
76
- props = JSON.parse(propsRaw);
77
- } catch (e) {
78
- console.error('[StipaVue] Failed to parse props for "' + name + '":', e);
79
- }
59
+ let props = {}
60
+ const propsRaw = el.getAttribute(PROPS_ATTR)
61
+ if (propsRaw) {
62
+ try {
63
+ props = JSON.parse(propsRaw)
64
+ } catch (e) {
65
+ console.error(`[StipaVue] Failed to parse props for "${name}":`, e)
80
66
  }
67
+ }
81
68
 
82
- const app = Vue.createApp(component, props);
83
- app.mount(el);
84
-
85
- el.setAttribute(MOUNTED_ATTR, "1");
86
- mounted.push({ app, el });
87
- });
88
- },
69
+ const app = createApp(component, props)
70
+ app.mount(el)
89
71
 
90
- unmountAll() {
91
- mounted.forEach((entry) => {
92
- entry.app.unmount();
93
- entry.el.removeAttribute(MOUNTED_ATTR);
94
- });
95
- mounted.length = 0;
96
- },
97
- };
72
+ el.setAttribute(MOUNTED_ATTR, '1')
73
+ mounted.push({ app, el })
74
+ })
75
+ },
98
76
 
99
- // DOMContentLoaded handles the synchronous registration pattern:
100
- // components registered via classic <script> tags before this event fires
101
- // will be mounted automatically. For async/module-based registration,
102
- // call StipaVue.mount() manually after registering.
103
- document.addEventListener("DOMContentLoaded", () => {
104
- StipaVue.mount();
105
- });
77
+ unmountAll() {
78
+ mounted.forEach((entry) => {
79
+ entry.app.unmount()
80
+ entry.el.removeAttribute(MOUNTED_ATTR)
81
+ })
82
+ mounted.length = 0
83
+ },
84
+ }
106
85
 
107
- window.StipaVue = StipaVue;
108
- })();
86
+ window.StipaVue = StipaVue
87
+ export { StipaVue }
88
+ export default StipaVue
@@ -23,6 +23,7 @@ module Stipa
23
23
 
24
24
  def files
25
25
  {
26
+ '.gitignore' => t_gitignore,
26
27
  'Gemfile' => t_gemfile,
27
28
  'server.rb' => t_server,
28
29
  'config/routes.rb' => t_routes(
@@ -34,6 +35,10 @@ module Stipa
34
35
  }
35
36
  end
36
37
 
38
+ def t_gitignore
39
+ t_gitignore_common
40
+ end
41
+
37
42
  def t_server
38
43
  <<~RUBY
39
44
  require 'stipa'
@@ -23,6 +23,7 @@ module Stipa
23
23
  make_dirs
24
24
  write_files
25
25
  post_generate
26
+ init_git
26
27
  say done_message
27
28
  end
28
29
 
@@ -41,14 +42,46 @@ module Stipa
41
42
 
42
43
  def post_generate = nil
43
44
 
45
+ def init_git
46
+ return unless git_available?
47
+
48
+ Dir.chdir(target) do
49
+ system('git init -q')
50
+ system('git add .')
51
+ system("git commit -q -m 'Initial commit'")
52
+ end
53
+ say ' git initialized repository with initial commit'
54
+ rescue => e
55
+ say " warn git init failed: #{e.message}"
56
+ end
57
+
44
58
  def say(msg) = puts(msg)
45
59
 
46
60
  def app_title
47
61
  name.split(/[-_]/).map(&:capitalize).join(' ')
48
62
  end
49
63
 
64
+ def git_available?
65
+ system('git --version > /dev/null 2>&1')
66
+ end
67
+
50
68
  # ── Shared templates ───────────────────────────────────────────────────────
51
69
 
70
+ def t_gitignore_common
71
+ <<~GITIGNORE
72
+ # Ruby
73
+ .bundle/
74
+ vendor/bundle/
75
+ Gemfile.lock
76
+
77
+ # OS
78
+ .DS_Store
79
+ Thumbs.db
80
+ *.swp
81
+ *.swo
82
+ GITIGNORE
83
+ end
84
+
52
85
  def t_gemfile
53
86
  <<~RUBY
54
87
  source 'https://rubygems.org'
@@ -9,13 +9,13 @@ module Stipa
9
9
 
10
10
  def dirs
11
11
  %w[
12
- src/config
13
- src/controllers
14
- src/models
15
- src/views/layouts
16
- src/views/home
17
- src/components
18
- public/components
12
+ app/config
13
+ app/controllers
14
+ app/models
15
+ app/views/layouts
16
+ app/views/home
17
+ app/components
18
+ public/vendor
19
19
  ]
20
20
  end
21
21
 
@@ -42,42 +42,65 @@ module Stipa
42
42
  cd #{name}
43
43
  bundle install
44
44
  npm install
45
- npm run build # compile Vue components
45
+ npm run build # copies Vue from node_modules + compiles app bundle
46
46
  bundle exec ruby server.rb
47
+
48
+ To upgrade Vue:
49
+ npm install vue@3.x.x
50
+ npm run build
47
51
  DONE
48
52
  end
49
53
 
50
54
  def files
51
55
  {
56
+ '.gitignore' => t_gitignore,
52
57
  'Gemfile' => t_gemfile,
53
58
  'package.json' => t_package_json,
54
59
  'rollup.config.js' => t_rollup_config,
55
60
  'tsconfig.json' => t_tsconfig,
56
61
  'server.rb' => t_server,
57
- 'src/config/routes.rb' => t_routes(
62
+ 'app/config/routes.rb' => t_routes(
58
63
  extra_requires: ['../controllers/home_controller', '../controllers/health_controller'],
59
64
  extra_routes: ["get '/', to: 'home#index'", "get '/api/health', to: 'health#show'"],
60
65
  method_override: true,
61
66
  ),
62
- 'src/controllers/application_controller.rb' => t_application_controller,
63
- 'src/controllers/home_controller.rb' => t_home_controller,
64
- 'src/controllers/health_controller.rb' => t_health_controller,
65
- 'src/views/layouts/application.html.erb' => t_layout,
66
- 'src/views/home/index.html.erb' => t_home_index,
67
+ 'app/controllers/application_controller.rb' => t_application_controller,
68
+ 'app/controllers/home_controller.rb' => t_home_controller,
69
+ 'app/controllers/health_controller.rb' => t_health_controller,
70
+ 'app/views/layouts/application.html.erb' => t_layout,
71
+ 'app/views/home/index.html.erb' => t_home_index,
67
72
  'public/app.css' => t_app_css,
68
- 'src/components/RequestCard.vue' => t_request_card_vue,
73
+ 'app/components/RequestCard.vue' => t_request_card_vue,
74
+ 'app/main.ts' => t_main_ts,
75
+ 'app/shims-vue.d.ts' => t_shims_vue,
69
76
  }
70
77
  end
71
78
 
79
+ def t_gitignore
80
+ t_gitignore_common + <<~GITIGNORE
81
+
82
+ # Node
83
+ node_modules/
84
+ package-lock.json
85
+
86
+ # Build output
87
+ public/app.js
88
+ public/app.js.map
89
+ public/vendor/
90
+ GITIGNORE
91
+ end
92
+
72
93
  def t_package_json
73
94
  JSON.pretty_generate(
74
95
  name: name,
75
96
  private: true,
76
97
  type: 'module',
77
98
  scripts: {
78
- build: 'rollup -c',
79
- watch: 'rollup -c --watch',
80
- dev: 'concurrently "bundle exec ruby server.rb" "rollup -c --watch"',
99
+ 'copy:vue' => 'cp node_modules/vue/dist/vue.esm-browser.prod.js public/vendor/vue.esm-browser.prod.js',
100
+ build: 'npm run copy:vue && rollup -c',
101
+ watch: 'rollup -c --watch',
102
+ dev: 'npm run copy:vue && concurrently "STIPA_RELOAD=1 bundle exec ruby server.rb" "rollup -c --watch"',
103
+ typecheck: 'vue-tsc --noEmit',
81
104
  },
82
105
  devDependencies: {
83
106
  'concurrently' => '^8.0.0',
@@ -87,6 +110,7 @@ module Stipa
87
110
  '@vue/compiler-sfc' => '^3.4.0',
88
111
  'typescript' => '^5.0.0',
89
112
  'vue' => '^3.4.0',
113
+ 'vue-tsc' => '^2.0.0',
90
114
  },
91
115
  ) + "\n"
92
116
  end
@@ -95,30 +119,18 @@ module Stipa
95
119
  <<~JS
96
120
  import vue from 'rollup-plugin-vue'
97
121
  import typescript from '@rollup/plugin-typescript'
98
- import { readdirSync } from 'fs'
99
-
100
- const src = './src/components'
101
- const out = './public/components'
102
-
103
- const inputs = readdirSync(src).filter(f => f.endsWith('.vue') || f.endsWith('.ts'))
104
-
105
- export default inputs.map(file => {
106
- const name = file.replace(/\\.(vue|ts)$/, '')
107
- const isTs = file.endsWith('.ts')
108
- return {
109
- input: `${src}/${file}`,
110
- output: {
111
- file: `${out}/${name}.js`,
112
- format: 'iife',
113
- name,
114
- globals: { vue: 'Vue' },
115
- },
116
- external: ['vue'],
117
- // rollup-plugin-vue handles <script lang="ts"> internally;
118
- // @rollup/plugin-typescript is only needed for plain .ts files.
119
- plugins: [vue(), ...(isTs ? [typescript({ tsconfig: './tsconfig.json' })] : [])],
120
- }
121
- })
122
+
123
+ export default {
124
+ input: 'app/main.ts',
125
+ output: {
126
+ file: 'public/app.js',
127
+ format: 'es',
128
+ },
129
+ external: ['vue'],
130
+ // rollup-plugin-vue handles <script lang="ts"> in .vue SFCs;
131
+ // @rollup/plugin-typescript compiles app/main.ts and other plain .ts files.
132
+ plugins: [vue(), typescript({ tsconfig: './tsconfig.json' })],
133
+ }
122
134
  JS
123
135
  end
124
136
 
@@ -130,20 +142,21 @@ module Stipa
130
142
  moduleResolution: 'bundler',
131
143
  strict: true,
132
144
  skipLibCheck: true,
145
+ allowJs: true,
133
146
  },
134
- include: ['src/**/*'],
147
+ include: ['app/**/*'],
135
148
  ) + "\n"
136
149
  end
137
150
 
138
151
  def t_server
139
152
  <<~RUBY
140
153
  require 'stipa'
141
- require_relative 'src/config/routes'
154
+ require_relative 'app/config/routes'
142
155
 
143
156
  APP_DIR = __dir__
144
157
 
145
158
  app = Stipa::App.new(
146
- views: "\#{APP_DIR}/src/views",
159
+ views: "\#{APP_DIR}/app/views",
147
160
  public: "\#{APP_DIR}/public",
148
161
  )
149
162
 
@@ -261,17 +274,16 @@ module Stipa
261
274
  <link rel="icon" href="/favicon.ico">
262
275
  <%= stylesheet_tag '/app.css' %>
263
276
 
264
- <%# Vue 3 global buildsets window.Vue %>
265
- <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
277
+ <%# Pin Vue version via importmapupgrade by changing vue in package.json and rebuilding %>
278
+ <script type="importmap">
279
+ { "imports": { "vue": "/vendor/vue.esm-browser.prod.js" } }
280
+ </script>
266
281
 
267
- <%# Stipa Vue bootstrapper — sets window.StipaVue, auto-mounts on DOMContentLoaded %>
282
+ <%# Stipa Vue bootstrapper — exposes window.StipaVue %>
268
283
  <%= stipa_vue_bootstrap %>
269
284
 
270
- <%# Compiled Vue components add one block per component %>
271
- <script src="/components/RequestCard.js"></script>
272
- <script>
273
- window.StipaVue.register('RequestCard', window.RequestCard)
274
- </script>
285
+ <%# App bundleregisters components and mounts them %>
286
+ <script type="module" src="/app.js"></script>
275
287
 
276
288
  <%# Measure time from first byte to DOMContentLoaded %>
277
289
  <script>const _t0 = performance.now()</script>
@@ -396,6 +408,28 @@ module Stipa
396
408
 
397
409
 
398
410
 
411
+ def t_shims_vue
412
+ <<~TS
413
+ declare module '*.vue' {
414
+ import type { DefineComponent } from 'vue'
415
+ const component: DefineComponent
416
+ export default component
417
+ }
418
+ TS
419
+ end
420
+
421
+ def t_main_ts
422
+ <<~TS
423
+ import RequestCard from './components/RequestCard.vue'
424
+
425
+ // window.StipaVue is set by /stipa-vue.js, loaded as a module before this script.
426
+ const { StipaVue } = window as any
427
+
428
+ StipaVue.register('RequestCard', RequestCard)
429
+ StipaVue.mount()
430
+ TS
431
+ end
432
+
399
433
  def t_request_card_vue
400
434
  <<~VUE
401
435
  <template>
@@ -0,0 +1,85 @@
1
+ module Stipa
2
+ # Development file watcher that restarts the process when Ruby source files change.
3
+ #
4
+ # Enabled when:
5
+ # - STIPA_RELOAD=1 environment variable is set, OR
6
+ # - reload: true is passed to App#start / Server#start
7
+ #
8
+ # Strategy:
9
+ # A background thread polls the mtime of all .rb files visible to Ruby
10
+ # ($LOADED_FEATURES) plus any extra watch paths supplied by the user.
11
+ # When a change is detected it calls exec($0, *ARGV) which replaces the
12
+ # current process image with a fresh one — same PID namespace, same
13
+ # command line arguments, all changes picked up from scratch.
14
+ #
15
+ # Why exec instead of in-process reload:
16
+ # In-process reload requires clearing constants, unloading files, and
17
+ # rebuilding the route table. exec is simpler, safer, and handles any
18
+ # kind of change (routes, middleware, config, gems) without edge cases.
19
+ class Reloader
20
+ DEFAULT_INTERVAL = 0.5 # seconds between polls
21
+
22
+ def initialize(logger:, interval: DEFAULT_INTERVAL, watch: [])
23
+ @logger = logger
24
+ @interval = interval
25
+ @extra = Array(watch).map { |p| File.expand_path(p) }
26
+ @mtimes = {}
27
+ @thread = nil
28
+ end
29
+
30
+ def start
31
+ snapshot!
32
+ @thread = Thread.new { watch_loop }
33
+ @thread.name = 'stipa-reloader'
34
+ @thread.abort_on_exception = false
35
+ @logger.warn('reloader active — watching for file changes')
36
+ end
37
+
38
+ def stop
39
+ @thread&.kill
40
+ end
41
+
42
+ private
43
+
44
+ def watch_loop
45
+ loop do
46
+ sleep @interval
47
+ if changed?
48
+ @logger.warn('file change detected — restarting')
49
+ $stdout.flush
50
+ $stderr.flush
51
+ exec($0, *ARGV)
52
+ end
53
+ end
54
+ rescue => e
55
+ @logger.error("reloader crashed: #{e.class}: #{e.message}")
56
+ end
57
+
58
+ # Collect the current set of watched files: everything Ruby has loaded
59
+ # plus any extra paths the user specified.
60
+ def watched_files
61
+ ($LOADED_FEATURES + @extra).uniq
62
+ end
63
+
64
+ def snapshot!
65
+ watched_files.each do |path|
66
+ @mtimes[path] = mtime(path)
67
+ end
68
+ end
69
+
70
+ def changed?
71
+ watched_files.any? do |path|
72
+ current = mtime(path)
73
+ previous = @mtimes[path]
74
+ @mtimes[path] = current
75
+ current != previous
76
+ end
77
+ end
78
+
79
+ def mtime(path)
80
+ File.mtime(path)
81
+ rescue Errno::ENOENT
82
+ nil
83
+ end
84
+ end
85
+ end
data/lib/stipa/server.rb CHANGED
@@ -53,6 +53,7 @@ module Stipa
53
53
  max_body_size: 1 * 1024 * 1024,
54
54
  backpressure: :drop, # :drop (503) or :block (wait briefly)
55
55
  log_level: :info,
56
+ reload: ENV['STIPA_RELOAD'] == '1',
56
57
  }.freeze
57
58
 
58
59
  def initialize(app:, **overrides)
@@ -64,7 +65,8 @@ module Stipa
64
65
  queue_depth: @config[:queue_depth],
65
66
  on_error: method(:pool_error),
66
67
  )
67
- @running = false
68
+ @running = false
69
+ @reloader = @config[:reload] ? Reloader.new(logger: @logger) : nil
68
70
  end
69
71
 
70
72
  # Start the server. Blocks until SIGTERM/SIGINT.
@@ -73,6 +75,7 @@ module Stipa
73
75
  @running = true
74
76
 
75
77
  register_signals
78
+ @reloader&.start
76
79
 
77
80
  @logger.info(
78
81
  req: nil, res: nil,
@@ -85,6 +88,7 @@ module Stipa
85
88
 
86
89
  accept_loop
87
90
  ensure
91
+ @reloader&.stop
88
92
  @server&.close rescue nil
89
93
  @logger.info(req: nil, res: nil, msg: 'Stīpa stopped')
90
94
  end
@@ -156,7 +156,7 @@ module Stipa
156
156
  #
157
157
  # <%= stipa_vue_bootstrap %>
158
158
  def stipa_vue_bootstrap(src: '/stipa-vue.js')
159
- %(<script src="#{src}"></script>)
159
+ %(<script type="module" src="#{src}"></script>)
160
160
  end
161
161
 
162
162
  # Include one or more JavaScript files.
data/lib/stipa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stipa
2
- VERSION = "0.1.0"
2
+ VERSION = '0.1.2'
3
3
  end
data/lib/stipa.rb CHANGED
@@ -20,6 +20,7 @@
20
20
  require_relative 'stipa/version'
21
21
  require_relative 'stipa/logger'
22
22
  require_relative 'stipa/thread_pool'
23
+ require_relative 'stipa/reloader'
23
24
  require_relative 'stipa/middleware'
24
25
  require_relative 'stipa/static'
25
26
  require_relative 'stipa/template'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stipa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Harbs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-18 00:00:00.000000000 Z
11
+ date: 2026-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -100,6 +100,7 @@ files:
100
100
  - lib/stipa/generators/vue.rb
101
101
  - lib/stipa/logger.rb
102
102
  - lib/stipa/middleware.rb
103
+ - lib/stipa/reloader.rb
103
104
  - lib/stipa/request.rb
104
105
  - lib/stipa/response.rb
105
106
  - lib/stipa/server.rb