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 +4 -4
- data/lib/js/stipa-vue.js +63 -83
- data/lib/stipa/generators/api.rb +5 -0
- data/lib/stipa/generators/base.rb +33 -0
- data/lib/stipa/generators/vue.rb +87 -53
- data/lib/stipa/reloader.rb +85 -0
- data/lib/stipa/server.rb +5 -1
- data/lib/stipa/template.rb +1 -1
- data/lib/stipa/version.rb +1 -1
- data/lib/stipa.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c22dd3975ab3276a80054b24f2b9e04912139c30e57d2500ffbc79bdf9de37da
|
|
4
|
+
data.tar.gz: 8013ed53bddd21485caff447749c2c36fcdba2344930e283dc70003097b40337
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
11
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
29
|
-
|
|
22
|
+
const COMPONENT_ATTR = 'data-vue-component'
|
|
23
|
+
const PROPS_ATTR = 'data-props'
|
|
24
|
+
const MOUNTED_ATTR = 'data-stipa-vue-mounted'
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
registry[name] = component;
|
|
34
|
-
},
|
|
26
|
+
const registry = {}
|
|
27
|
+
const mounted = []
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
const StipaVue = {
|
|
30
|
+
register(name, component) {
|
|
31
|
+
registry[name] = component
|
|
32
|
+
},
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
44
|
+
const selector = `[${COMPONENT_ATTR}]:not([${MOUNTED_ATTR}])`
|
|
45
|
+
const elements = root.querySelectorAll(selector)
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
elements.forEach((el) => {
|
|
48
|
+
const name = el.getAttribute(COMPONENT_ATTR)
|
|
49
|
+
const component = registry[name]
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
91
|
-
mounted.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
mounted.length = 0;
|
|
96
|
-
},
|
|
97
|
-
};
|
|
72
|
+
el.setAttribute(MOUNTED_ATTR, '1')
|
|
73
|
+
mounted.push({ app, el })
|
|
74
|
+
})
|
|
75
|
+
},
|
|
98
76
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
}
|
|
86
|
+
window.StipaVue = StipaVue
|
|
87
|
+
export { StipaVue }
|
|
88
|
+
export default StipaVue
|
data/lib/stipa/generators/api.rb
CHANGED
|
@@ -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'
|
data/lib/stipa/generators/vue.rb
CHANGED
|
@@ -9,13 +9,13 @@ module Stipa
|
|
|
9
9
|
|
|
10
10
|
def dirs
|
|
11
11
|
%w[
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
public/
|
|
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 #
|
|
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
|
-
'
|
|
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
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
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
|
-
'
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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: ['
|
|
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 '
|
|
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}/
|
|
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
|
|
265
|
-
<script
|
|
277
|
+
<%# Pin Vue version via importmap — upgrade 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 —
|
|
282
|
+
<%# Stipa Vue bootstrapper — exposes window.StipaVue %>
|
|
268
283
|
<%= stipa_vue_bootstrap %>
|
|
269
284
|
|
|
270
|
-
<%#
|
|
271
|
-
<script src="/
|
|
272
|
-
<script>
|
|
273
|
-
window.StipaVue.register('RequestCard', window.RequestCard)
|
|
274
|
-
</script>
|
|
285
|
+
<%# App bundle — registers 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
|
|
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
|
data/lib/stipa/template.rb
CHANGED
data/lib/stipa/version.rb
CHANGED
data/lib/stipa.rb
CHANGED
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.
|
|
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-
|
|
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
|