proscenium 0.26.1-x86_64-linux-gnu → 0.27.0-x86_64-linux-gnu
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/README.md +50 -3
- data/lib/proscenium/app_package.rb +8 -4
- data/lib/proscenium/builder/request.rb +72 -0
- data/lib/proscenium/builder.rb +10 -74
- data/lib/proscenium/cli/bun_linker.rb +2 -2
- data/lib/proscenium/cli/collisions.rb +7 -9
- data/lib/proscenium/cli/error.rb +58 -265
- data/lib/proscenium/cli/gem_archive.rb +4 -2
- data/lib/proscenium/cli/gem_check.rb +13 -11
- data/lib/proscenium/cli/gitignore.rb +2 -1
- data/lib/proscenium/cli/inspect.rb +12 -11
- data/lib/proscenium/cli/install.rb +35 -46
- data/lib/proscenium/cli/manager.rb +6 -6
- data/lib/proscenium/cli/platform_variants.rb +8 -8
- data/lib/proscenium/cli/pnpm_approvals.rb +3 -3
- data/lib/proscenium/cli/pnpm_workspace.rb +15 -14
- data/lib/proscenium/cli/project_lock.rb +3 -3
- data/lib/proscenium/cli/registration.rb +5 -5
- data/lib/proscenium/cli/reporter.rb +2 -2
- data/lib/proscenium/cli/verify.rb +9 -50
- data/lib/proscenium/cli.rb +5 -4
- data/lib/proscenium/contexts/map.rb +183 -0
- data/lib/proscenium/contexts/mapping_generation.rb +68 -0
- data/lib/proscenium/contexts/messages.rb +276 -0
- data/lib/proscenium/{cli/contexts.rb → contexts/plan.rb} +12 -12
- data/lib/proscenium/contexts/projection.rb +77 -0
- data/lib/proscenium/{cli → contexts}/rules.rb +1 -1
- data/lib/proscenium/contexts/stale.rb +174 -0
- data/lib/proscenium/contexts.rb +16 -0
- data/lib/proscenium/css_module/path.rb +1 -0
- data/lib/proscenium/css_module/transformer.rb +15 -0
- data/lib/proscenium/css_module.rb +3 -2
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/helper.rb +6 -1
- data/lib/proscenium/importer.rb +13 -1
- data/lib/proscenium/middleware/base.rb +0 -4
- data/lib/proscenium/middleware.rb +0 -2
- data/lib/proscenium/railtie.rb +4 -4
- data/lib/proscenium/resolver.rb +3 -1
- data/lib/proscenium/side_load.rb +6 -2
- data/lib/proscenium/source_path.rb +2 -0
- data/lib/proscenium/utils.rb +2 -1
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium.rb +5 -5
- metadata +10 -7
- data/lib/proscenium/context_map.rb +0 -181
- data/lib/proscenium/dependency_context.rb +0 -74
- data/lib/proscenium/mapping_generation.rb +0 -64
- data/lib/proscenium/stale_contexts.rb +0 -118
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d6c5afd9078a48be6ebca7a839a34e30a64ae1c5d82e38ae9ab660f2b35a4e4
|
|
4
|
+
data.tar.gz: 9441d7864ad57c4db79a9d1966b88c2bb90fc8f8cc9c92fe3b7a25824bdd9efe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3ca42f3ef2f473f07c45804e87b2b0b4299ad3984a8a37dddc0e477800265ab99fa3340eabfee1b14b36b60b0ee77c07504aa81e48e96f1690d48625be86bf0c
|
|
7
|
+
data.tar.gz: 877dcd79fc4693e3b06498a878f4d368fe66b1cba84f9ad9d21d7e68edb7df4fb88cdc1960274c1e860409963af914a747d898a7520d77912cabac74e6b62c3a
|
data/README.md
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
- [Resolution](#resolution)
|
|
57
57
|
- [Aliases](#aliases)
|
|
58
58
|
- [Pre-compilation](#pre-compilation)
|
|
59
|
+
- [Extending Proscenium](#extending-proscenium)
|
|
59
60
|
- [Puma `preload_app!` and Cluster Mode](#puma-preload_app-and-cluster-mode)
|
|
60
61
|
- [Thanks](#thanks)
|
|
61
62
|
- [Development](#development)
|
|
@@ -103,7 +104,8 @@ fine; if not, `gem update --system`. Bundler 2.5.6 or newer resolves the platfor
|
|
|
103
104
|
needing anything else from you.
|
|
104
105
|
|
|
105
106
|
Installing on a platform not listed here gets you the platform-less gem, which carries no compiled
|
|
106
|
-
library, and Proscenium will say so by name
|
|
107
|
+
library, and Proscenium will say so by name on its first build or resolve, rather than failing
|
|
108
|
+
somewhere inside FFI.
|
|
107
109
|
If your platform should be supported, please [open an
|
|
108
110
|
issue](https://github.com/joelmoss/proscenium/issues).
|
|
109
111
|
|
|
@@ -742,6 +744,8 @@ p {
|
|
|
742
744
|
|
|
743
745
|
A mixin must be defined at the root of a file. A `@define-mixin` inside a rule or an at-rule block such as `@media`, or inside another mixin, does not define anything: it is passed through as written, with a warning.
|
|
744
746
|
|
|
747
|
+
A mixin from another file whose definition is malformed is refused: its `@mixin` is left as written, with a "malformed CSS" warning, and the stylesheet's other mixins still expand. A definition is malformed when it holds a bad token, such as a string broken by a newline, or when the file ends before closing it, inside an open comment, `url(` or escape (as when it is saved mid-edit). Malformed CSS in the stylesheet itself is returned as written, for esbuild to report.
|
|
748
|
+
|
|
745
749
|
One stylesheet may expand at most 100,000 mixins, inserting at most 4 MiB (4,194,304 bytes) of mixin definitions, with mixins nested at most 100 deep, so a chain of mixins that each include another more than once cannot grow without bound. Past any limit, each remaining `@mixin` is left as written, with one warning.
|
|
746
750
|
|
|
747
751
|
Each stylesheet reports at most 100 warnings, plus one saying how many more were not reported and, past a limit, the mixin limit warning. A precompile sends at most 100 errors and 100 warnings in all, then one more message for each saying how many more there were, so in a build that noisy the limit warning can be one of those counted.
|
|
@@ -909,10 +913,12 @@ treated as test, and so is served and precompiled unminified.
|
|
|
909
913
|
|
|
910
914
|
## Resolution
|
|
911
915
|
|
|
912
|
-
Proscenium will serve files ending with any of these extension: `js,mjs,ts,css,jsx,tsx` from the following directories, and their sub-directories of your Rails application's root: `/app`, `/lib`, `/config`, `/node_modules
|
|
916
|
+
Proscenium will build and serve files ending with any of these extension: `js,mjs,ts,css,jsx,tsx` from the following directories, and their sub-directories of your Rails application's root: `/app`, `/lib`, `/config`, `/node_modules`.
|
|
913
917
|
|
|
914
918
|
So a file at `/app/views/users/index.js` will be served from `https://localhost:3000/app/views/users/index.js`.
|
|
915
919
|
|
|
920
|
+
`.js` and `.css` files under `/vendor` are served as they are, without building, and with `Cache-Control: public, max-age=3155695200, immutable`. A browser never asks for one again, so put a version in the path of each vendored file (`/vendor/foo/1.2.3/foo.js`), and change the path when the file changes. This suits a package you load at run time rather than bundle, such as one marked `external`.
|
|
921
|
+
|
|
916
922
|
You can continue to access any file in the `/public` directory as you normally would. Proscenium will not process files in the `/public` directory.
|
|
917
923
|
|
|
918
924
|
If requesting a file that exists in a root directory and the public directory, the file in the public directory will be served. For example, if you have a file at `/lib/foo.js` and `/public/lib/foo.js`, and you request `/lib/foo.js`, the file in the public directory (`/public/lib/foo.js`) will be served.
|
|
@@ -959,11 +965,52 @@ Rails.configuration.proscenium.precompile = Set[
|
|
|
959
965
|
|
|
960
966
|
This will bundle, code split, tree shake, and compile all your JS, TS, JSX, TSX and CSS files and place them in the `public/assets` directory, ready to be served in production.
|
|
961
967
|
|
|
968
|
+
## Extending Proscenium
|
|
969
|
+
|
|
970
|
+
Gems that bring side loading and CSS modules to other component libraries, such as
|
|
971
|
+
[proscenium-phlex](https://github.com/joelmoss/proscenium-phlex) and
|
|
972
|
+
[proscenium-view_component](https://github.com/joelmoss/proscenium-view_component), build on a
|
|
973
|
+
small public API. Each method below is tagged `@api public` and has its own tests, so it changes
|
|
974
|
+
only with a note in the release.
|
|
975
|
+
|
|
976
|
+
| API | What it does |
|
|
977
|
+
| --- | --- |
|
|
978
|
+
| `Proscenium::SideLoad.sideload_inheritance_chain(obj, options)` | Side loads the assets of `obj`'s class and each superclass, as described below. |
|
|
979
|
+
| `Proscenium::SideLoad.merge_options(base, override, receiver)` | Merges side load options as Proscenium does for views: a new Hash, keys symbolized at every depth, any `css`/`js` Proc evaluated against `receiver`. |
|
|
980
|
+
| `Proscenium::Importer.import(path, sideloaded: false, **options)` | Imports one asset for the current request, once, merging repeat options (later ones win). A truthy `sideloaded` sends a `sideload.proscenium` notification. |
|
|
981
|
+
| `Proscenium::Importer.each_stylesheet(delete: false)`, `each_javascript(delete: false)` | Yield each imported stylesheet or script with its options, for an app that writes its own tags. `delete: true` also removes each one from the imports, so it is written only once. |
|
|
982
|
+
| `Proscenium::Importer.sideload(path, **options)` | Imports the script and stylesheet beside the Ruby file at `path`, an absolute `Pathname` (anything else raises `ArgumentError`). Does nothing when side loading is off, or `js:` and `css:` are both `false`. `lazy: true` imports a script but writes no tag for it, for a component that loads it itself. |
|
|
983
|
+
| `Proscenium::CssModule` | Included into a component, against its `css_module_path`: `css_module` transforms every name, `class_names` only `@`-prefixed ones and paths (a name with a `/`, such as `/lib/users@name`), and the private `cssm` is the component's `Transformer`. |
|
|
984
|
+
| `Proscenium::CssModule::Path` | Extended by a component class: `css_module_path` is the `.module.css` beside its source file, else the nearest ancestor's. |
|
|
985
|
+
| `Proscenium::CssModule::Transformer#class_names(*names, require_prefix: true)` | Transforms names; given a block, yields each transformed name and the path of the CSS module it side loads, if any. |
|
|
986
|
+
| `Proscenium::CssModule::Transformer#class_name!(name, original_name, path: source_path)` | Transforms one name, whatever its prefix, against the CSS module at `path`, and side loads that module. Raises `TransformError`, naming `original_name`, when there is no path. |
|
|
987
|
+
| `Proscenium::SourcePath` | Included into a class: `source_path` is the file the class is defined in. |
|
|
988
|
+
| `Proscenium::Resolver.resolve(path)` | Where an asset is served, for an absolute file or URL path, or a bare specifier such as an NPM package (a `./` or `../` path raises `ArgumentError`): its URL, or, when a loaded manifest has it, the entry's outputs as an Array, its own first. `as_array: true` returns `[manifest outputs, URL, file]`. |
|
|
989
|
+
| `Proscenium::Utils.merge_bang_attributes!(names, attributes, allowed)` | Turns an allowed `:name!` argument into `name: true`, unless `name` was given as a keyword. |
|
|
990
|
+
|
|
991
|
+
Side loading and CSS module lookup walk a component's class hierarchy through these methods,
|
|
992
|
+
which the component's classes provide:
|
|
993
|
+
|
|
994
|
+
- `source_path` on each class: the Ruby file whose sibling `.js`/`.ts`/`.jsx`/`.tsx`, `.css` and
|
|
995
|
+
`.module.css` files are side loaded.
|
|
996
|
+
- `abstract_class` on each class, optional: when true, the walk stops there, and neither that
|
|
997
|
+
class nor any above it is side loaded or searched for a CSS module.
|
|
998
|
+
- `sideload(options)` on each class, optional: called after that class's own assets, to side load
|
|
999
|
+
anything else.
|
|
1000
|
+
- `sideload_assets_options` on the instance: its own side load options, merged over the ones
|
|
1001
|
+
given.
|
|
1002
|
+
- `css_module_path` on each class, from `CssModule::Path`: the class's CSS module, used by
|
|
1003
|
+
`CssModule` and walked the same way, ending at an abstract class.
|
|
1004
|
+
|
|
1005
|
+
The walk side loads each class's script, starting with the component's own, then the first CSS
|
|
1006
|
+
module found, then each class's stylesheet starting with the topmost, so a component's styles
|
|
1007
|
+
come after the ones it inherits.
|
|
1008
|
+
|
|
962
1009
|
## Puma `preload_app!` and Cluster Mode
|
|
963
1010
|
|
|
964
1011
|
Proscenium's builder is backed by a Go shared library, and Go's runtime has a known, unfixed limitation ([golang/go#15538](https://github.com/golang/go/issues/15538)): if the Go runtime has already been initialized in a process before that process calls `fork()`, the forked child's Go runtime is left in a broken state (only the forking thread survives `fork()`; the Go scheduler and GC's other threads simply vanish) and any subsequent call into Go code in that child can hang or fail.
|
|
965
1012
|
|
|
966
|
-
This matters if you run Puma in cluster mode with `preload_app!` (the app, including gems, is booted once in the master process, then workers are created via `fork()` with no `exec()` afterward). Proscenium itself never triggers this -
|
|
1013
|
+
This matters if you run Puma in cluster mode with `preload_app!` (the app, including gems, is booted once in the master process, then workers are created via `fork()` with no `exec()` afterward). Proscenium itself never triggers this - requiring the gem does not initialize the Go runtime, and neither does naming `Proscenium::Builder` or one of its error classes (so a `rescue_from Proscenium::Builder::BuildError` in a controller is safe), and nothing in Proscenium's own boot sequence calls into it. The library is opened, and the Go runtime initialized, the first time something actually calls a builder method (`build_to_string`, `resolve`, or `compile`).
|
|
967
1014
|
|
|
968
1015
|
**Do not call any `Proscenium::Builder` method (directly, or indirectly via the resolver/side-loading) from a Rails initializer or any other code that runs during application boot**, if you use `preload_app!` with `workers`. Doing so initializes the Go runtime in the master process before the fork, and every worker will inherit a broken one. There is no fix available from Go's side - this is a fundamental fork() limitation, not a bug Proscenium can work around. Asset builds and resolves triggered by actual HTTP requests (the normal case) are unaffected, since those always happen after the fork, independently in each worker.
|
|
969
1016
|
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
|
|
5
5
|
module Proscenium
|
|
6
|
-
# What the app's own package.json says that the context map needs (#154). Like
|
|
7
|
-
# it reads JSON through, it loads nothing the `proscenium` CLI may not.
|
|
6
|
+
# What the app's own package.json says that the context map needs (#154). Like Contexts::Map,
|
|
7
|
+
# which it reads JSON through, it loads nothing the `proscenium` CLI may not.
|
|
8
8
|
module AppPackage
|
|
9
9
|
LOCAL_SPECS = %w[link: file: workspace: portal:].freeze
|
|
10
|
+
# The fields an app's own dependencies are under. Peers too: pnpm installs the app's own peers
|
|
11
|
+
# at the root.
|
|
12
|
+
DEPENDENCY_FIELDS = %w[dependencies devDependencies optionalDependencies
|
|
13
|
+
peerDependencies].freeze
|
|
10
14
|
|
|
11
15
|
module_function
|
|
12
16
|
|
|
@@ -18,7 +22,7 @@ module Proscenium
|
|
|
18
22
|
package = read(root) or return []
|
|
19
23
|
|
|
20
24
|
members = bun?(root, package) ? workspace_members(root, package['workspaces']) : []
|
|
21
|
-
|
|
25
|
+
DEPENDENCY_FIELDS.flat_map do |field|
|
|
22
26
|
deps = package[field]
|
|
23
27
|
next [] unless deps.is_a?(Hash)
|
|
24
28
|
|
|
@@ -81,7 +85,7 @@ module Proscenium
|
|
|
81
85
|
# The app's package.json as an object, or nil when it is missing or not one.
|
|
82
86
|
def read(root)
|
|
83
87
|
path = File.join(root, 'package.json')
|
|
84
|
-
package = File.exist?(path) &&
|
|
88
|
+
package = File.exist?(path) && Contexts::Map.read_json(path)
|
|
85
89
|
package if package.is_a?(Hash)
|
|
86
90
|
rescue JSON::ParserError
|
|
87
91
|
nil
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
module Proscenium
|
|
6
|
+
class Builder
|
|
7
|
+
module Request
|
|
8
|
+
extend FFI::Library
|
|
9
|
+
|
|
10
|
+
raise UnsupportedPlatform, LIBRARY_PATH unless LIBRARY_PATH.exist?
|
|
11
|
+
|
|
12
|
+
ffi_lib LIBRARY_PATH.to_s
|
|
13
|
+
|
|
14
|
+
# Windows unmaps the library at interpreter exit, and the Go runtime does not survive it.
|
|
15
|
+
#
|
|
16
|
+
# ffi's library_free calls dl_close on every platform but macOS, which is FreeLibrary here,
|
|
17
|
+
# and Ruby runs every T_DATA free during VM teardown. Go's c-shared runtime cannot be
|
|
18
|
+
# unloaded: its threads are still running when the mapping goes. On Linux this never bites,
|
|
19
|
+
# because the Go linker marks the library nodelete and dlclose is a no-op; Windows has no
|
|
20
|
+
# equivalent. Measured on windows-latest: three of five plain `require` runs exited 139,
|
|
21
|
+
# while five of five exited 0 when the script ended in `exit!`, which skips the teardown.
|
|
22
|
+
#
|
|
23
|
+
# Pinning the module means the reference count never reaches zero, so FreeLibrary returns
|
|
24
|
+
# without unmapping anything. Nothing here needs the handle, only the pin.
|
|
25
|
+
if Gem.win_platform?
|
|
26
|
+
module Kernel32
|
|
27
|
+
extend FFI::Library
|
|
28
|
+
|
|
29
|
+
ffi_lib 'kernel32'
|
|
30
|
+
attach_function :GetModuleHandleExW, %i[uint32 buffer_in pointer], :int
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# GET_MODULE_HANDLE_EX_FLAG_PIN. Backslashes, because GetModuleHandle documents that a full
|
|
34
|
+
# path must use them, and LIBRARY_PATH is built from __dir__, which uses "/". Checked,
|
|
35
|
+
# because a pin that quietly failed would bring back an exit crash that only happens some
|
|
36
|
+
# of the time, with nothing to say why.
|
|
37
|
+
name = "#{LIBRARY_PATH.to_s.tr('/', '\\')}\0".encode(Encoding::UTF_16LE)
|
|
38
|
+
if Kernel32.GetModuleHandleExW(0x1, name, FFI::MemoryPointer.new(:pointer)).zero?
|
|
39
|
+
warn "Proscenium could not pin #{LIBRARY_PATH} (Windows error " \
|
|
40
|
+
"#{FFI::LastError.winapi_error}), so Ruby may crash when it exits."
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
enum :environment, [:development, 1, :test, :production]
|
|
45
|
+
|
|
46
|
+
# `blocking: true` releases the GVL for the duration of the call, so a build/resolve
|
|
47
|
+
# doesn't stall unrelated Ruby threads (eg. other requests in a multi-threaded server).
|
|
48
|
+
# Safe to do because the Go side shares nothing between calls: each call parses its own
|
|
49
|
+
# config, and the caches that outlive a call (i18n, svg, npm replacements) carry their own
|
|
50
|
+
# locks. See the note in main.go. There is no serialising mutex any more; a new piece of
|
|
51
|
+
# global state on the Go side needs its own.
|
|
52
|
+
|
|
53
|
+
attach_function :build_to_string, [
|
|
54
|
+
:string, # Path or entry point.
|
|
55
|
+
:pointer # Config as JSON.
|
|
56
|
+
], Result.by_value, blocking: true
|
|
57
|
+
|
|
58
|
+
attach_function :resolve, [
|
|
59
|
+
:string, # path or entry point
|
|
60
|
+
:pointer # Config as JSON.
|
|
61
|
+
], ResolveResult.by_value, blocking: true
|
|
62
|
+
|
|
63
|
+
attach_function :compile, [
|
|
64
|
+
:pointer # Config as JSON.
|
|
65
|
+
], CompileResult.by_value, blocking: true
|
|
66
|
+
|
|
67
|
+
attach_function :reset_config, [], :void, blocking: true
|
|
68
|
+
|
|
69
|
+
attach_function :free_cstr, [:pointer], :void
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/proscenium/builder.rb
CHANGED
|
@@ -55,70 +55,11 @@ module Proscenium
|
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
ffi_lib LIBRARY_PATH.to_s
|
|
64
|
-
|
|
65
|
-
# Windows unmaps the library at interpreter exit, and the Go runtime does not survive it.
|
|
66
|
-
#
|
|
67
|
-
# ffi's library_free calls dl_close on every platform but macOS, which is FreeLibrary here,
|
|
68
|
-
# and Ruby runs every T_DATA free during VM teardown. Go's c-shared runtime cannot be
|
|
69
|
-
# unloaded: its threads are still running when the mapping goes. On Linux this never bites,
|
|
70
|
-
# because the Go linker marks the library nodelete and dlclose is a no-op; Windows has no
|
|
71
|
-
# equivalent. Measured on windows-latest: three of five plain `require` runs exited 139,
|
|
72
|
-
# while five of five exited 0 when the script ended in `exit!`, which skips the teardown.
|
|
73
|
-
#
|
|
74
|
-
# Pinning the module means the reference count never reaches zero, so FreeLibrary returns
|
|
75
|
-
# without unmapping anything. Nothing here needs the handle, only the pin.
|
|
76
|
-
if Gem.win_platform?
|
|
77
|
-
module Kernel32
|
|
78
|
-
extend FFI::Library
|
|
79
|
-
|
|
80
|
-
ffi_lib 'kernel32'
|
|
81
|
-
attach_function :GetModuleHandleExW, %i[uint32 buffer_in pointer], :int
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# GET_MODULE_HANDLE_EX_FLAG_PIN. Backslashes, because GetModuleHandle documents that a full
|
|
85
|
-
# path must use them, and LIBRARY_PATH is built from __dir__, which uses "/". Checked,
|
|
86
|
-
# because a pin that quietly failed would bring back an exit crash that only happens some
|
|
87
|
-
# of the time, with nothing to say why.
|
|
88
|
-
name = "#{LIBRARY_PATH.to_s.tr('/', '\\')}\0".encode(Encoding::UTF_16LE)
|
|
89
|
-
if Kernel32.GetModuleHandleExW(0x1, name, FFI::MemoryPointer.new(:pointer)).zero?
|
|
90
|
-
warn "Proscenium could not pin #{LIBRARY_PATH} (Windows error " \
|
|
91
|
-
"#{FFI::LastError.winapi_error}), so Ruby may crash when it exits."
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
enum :environment, [:development, 1, :test, :production]
|
|
96
|
-
|
|
97
|
-
# `blocking: true` releases the GVL for the duration of the call, so a build/resolve
|
|
98
|
-
# doesn't stall unrelated Ruby threads (eg. other requests in a multi-threaded server).
|
|
99
|
-
# Safe to do because the Go side shares nothing between calls: each call parses its own
|
|
100
|
-
# config, and the caches that outlive a call (i18n, svg, npm replacements) carry their own
|
|
101
|
-
# locks. See the note in main.go. There is no serialising mutex any more; a new piece of
|
|
102
|
-
# global state on the Go side needs its own.
|
|
103
|
-
|
|
104
|
-
attach_function :build_to_string, [
|
|
105
|
-
:string, # Path or entry point.
|
|
106
|
-
:pointer # Config as JSON.
|
|
107
|
-
], Result.by_value, blocking: true
|
|
108
|
-
|
|
109
|
-
attach_function :resolve, [
|
|
110
|
-
:string, # path or entry point
|
|
111
|
-
:pointer # Config as JSON.
|
|
112
|
-
], ResolveResult.by_value, blocking: true
|
|
113
|
-
|
|
114
|
-
attach_function :compile, [
|
|
115
|
-
:pointer # Config as JSON.
|
|
116
|
-
], CompileResult.by_value, blocking: true
|
|
117
|
-
|
|
118
|
-
attach_function :reset_config, [], :void, blocking: true
|
|
119
|
-
|
|
120
|
-
attach_function :free_cstr, [:pointer], :void
|
|
121
|
-
end
|
|
58
|
+
# The FFI binding to the Go library. Autoloaded, so the library is opened (and the Go runtime
|
|
59
|
+
# started) by the first call into it, never by naming Builder or one of its error classes: a
|
|
60
|
+
# `rescue_from Proscenium::Builder::BuildError` in a controller loaded during a Puma
|
|
61
|
+
# `preload_app!` boot must not start Go before the fork (golang/go#15538).
|
|
62
|
+
autoload :Request, File.expand_path('builder/request', __dir__)
|
|
122
63
|
|
|
123
64
|
class BuildError < Error
|
|
124
65
|
attr_reader :error, :path
|
|
@@ -199,12 +140,12 @@ module Proscenium
|
|
|
199
140
|
# finished, or the committed gem dependency contexts are stale. With
|
|
200
141
|
# PROSCENIUM_STALE_CONTEXT=warn, for an incident, stale contexts are logged once instead.
|
|
201
142
|
def self.refusal(root = Rails.root)
|
|
202
|
-
project =
|
|
203
|
-
return
|
|
143
|
+
project = Contexts::Map.project_root
|
|
144
|
+
return Contexts::Map::INSTALLING_MESSAGE if Contexts::Map.installing?(project)
|
|
204
145
|
|
|
205
146
|
outside = outside_rails_root(project, root) and return outside
|
|
206
147
|
|
|
207
|
-
stale =
|
|
148
|
+
stale = Contexts::Stale.message(project, generation) or return
|
|
208
149
|
return stale unless ENV['PROSCENIUM_STALE_CONTEXT'] == 'warn'
|
|
209
150
|
|
|
210
151
|
@warned ||= Set.new
|
|
@@ -217,7 +158,7 @@ module Proscenium
|
|
|
217
158
|
# has no URL the engine serves, so such a layout is refused plainly, not left to fail
|
|
218
159
|
# mid-page. Only compares paths unless they differ.
|
|
219
160
|
def self.outside_rails_root(project, root = Rails.root)
|
|
220
|
-
return if within?(project, root) || !
|
|
161
|
+
return if within?(project, root) || !Contexts::Map.adopted?(project)
|
|
221
162
|
|
|
222
163
|
"Gem dependency contexts are under #{project}, outside the Rails app at #{root}, " \
|
|
223
164
|
'which Proscenium cannot serve yet. Keep the Gemfile in the Rails root to use them.'
|
|
@@ -270,7 +211,6 @@ module Proscenium
|
|
|
270
211
|
config_hash = {
|
|
271
212
|
RootPath: (@root || Rails.root).to_s,
|
|
272
213
|
OutputDir: "public#{Proscenium.config.output_dir}",
|
|
273
|
-
GemPath: gem_root,
|
|
274
214
|
Environment: ENVIRONMENTS.fetch(Rails.env.to_sym, 2),
|
|
275
215
|
EnvVars: env_vars,
|
|
276
216
|
CodeSplitting: Proscenium.config.code_splitting,
|
|
@@ -280,7 +220,7 @@ module Proscenium
|
|
|
280
220
|
External: Proscenium.config.external,
|
|
281
221
|
Precompile: Proscenium.config.precompile,
|
|
282
222
|
Debug: Proscenium.config.debug,
|
|
283
|
-
**
|
|
223
|
+
**Contexts::Map.config(Contexts::Map.project_root, generation)
|
|
284
224
|
}.merge(@overrides)
|
|
285
225
|
|
|
286
226
|
self.class.request_config_pointer(config_hash)
|
|
@@ -395,9 +335,5 @@ module Proscenium
|
|
|
395
335
|
ENV['NODE_ENV'] = ENV.fetch('RAILS_ENV', nil)
|
|
396
336
|
ENV.slice(*Proscenium::DEFAULT_ENV_VARS, *names)
|
|
397
337
|
end
|
|
398
|
-
|
|
399
|
-
def gem_root
|
|
400
|
-
Pathname.new(__dir__).join('..', '..').to_s
|
|
401
|
-
end
|
|
402
338
|
end
|
|
403
339
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
-
require_relative '../
|
|
4
|
+
require_relative '../contexts/map'
|
|
5
5
|
require_relative 'manager'
|
|
6
6
|
|
|
7
7
|
module Proscenium
|
|
@@ -65,7 +65,7 @@ module Proscenium
|
|
|
65
65
|
|
|
66
66
|
def workspaces?(root)
|
|
67
67
|
path = File.join(root, 'package.json')
|
|
68
|
-
File.exist?(path) &&
|
|
68
|
+
File.exist?(path) && Contexts::Map.read_json(path).key?('workspaces')
|
|
69
69
|
rescue JSON::ParserError
|
|
70
70
|
false
|
|
71
71
|
end
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'yaml'
|
|
5
|
-
require_relative '
|
|
5
|
+
require_relative '../app_package'
|
|
6
|
+
require_relative '../contexts/map'
|
|
6
7
|
|
|
7
8
|
module Proscenium
|
|
8
9
|
module CLI
|
|
@@ -12,14 +13,11 @@ module Proscenium
|
|
|
12
13
|
module Collisions
|
|
13
14
|
module_function
|
|
14
15
|
|
|
15
|
-
DEPENDENCY_FIELDS = %w[dependencies devDependencies optionalDependencies
|
|
16
|
-
peerDependencies].freeze
|
|
17
|
-
|
|
18
16
|
# One line for each package of the app's own taking a context's name.
|
|
19
17
|
def find(root, manager, gems)
|
|
20
18
|
names = gems.to_h { ["@rubygems/#{it}", it] }
|
|
21
19
|
app = read_json(File.join(root, 'package.json')) || {}
|
|
22
|
-
pinned = DEPENDENCY_FIELDS.flat_map do |field|
|
|
20
|
+
pinned = AppPackage::DEPENDENCY_FIELDS.flat_map do |field|
|
|
23
21
|
(app[field] || {}).filter_map do |name, spec|
|
|
24
22
|
next if !names.key?(name) || context_workspace?(spec, name) ||
|
|
25
23
|
context_link?(root, spec, names[name])
|
|
@@ -46,7 +44,7 @@ module Proscenium
|
|
|
46
44
|
def context_link?(root, spec, gem)
|
|
47
45
|
return false unless spec.to_s.start_with?('link:')
|
|
48
46
|
|
|
49
|
-
context = File.join(File.expand_path(root),
|
|
47
|
+
context = File.join(File.expand_path(root), Contexts::Map::CONTEXTS, gem)
|
|
50
48
|
File.expand_path(spec.to_s.delete_prefix('link:'), root) == context
|
|
51
49
|
end
|
|
52
50
|
|
|
@@ -55,7 +53,7 @@ module Proscenium
|
|
|
55
53
|
# Bun read `./packages/*` and `packages/*` alike.
|
|
56
54
|
def workspace_packages(root, manager, app)
|
|
57
55
|
excluded, included = workspace_patterns(root, manager, app)
|
|
58
|
-
.grep(String).reject {
|
|
56
|
+
.grep(String).reject { Contexts::Map.registration?(it) }
|
|
59
57
|
.map { it.sub(%r{\A(!?)\./}, '\\1') }
|
|
60
58
|
.partition { it.start_with?('!') }
|
|
61
59
|
files = included.flat_map { Dir.glob(File.join(it, 'package.json'), base: root) }.uniq
|
|
@@ -77,14 +75,14 @@ module Proscenium
|
|
|
77
75
|
Array(workspaces.is_a?(Hash) ? workspaces['packages'] : workspaces)
|
|
78
76
|
else
|
|
79
77
|
path = File.join(root, 'pnpm-workspace.yaml')
|
|
80
|
-
File.exist?(path) ? Array(
|
|
78
|
+
File.exist?(path) ? Array(Contexts::Map.yaml_packages(File.read(path))) : []
|
|
81
79
|
end
|
|
82
80
|
rescue Psych::SyntaxError
|
|
83
81
|
[] # the manager reports it
|
|
84
82
|
end
|
|
85
83
|
|
|
86
84
|
def read_json(path)
|
|
87
|
-
json = File.exist?(path) &&
|
|
85
|
+
json = File.exist?(path) && Contexts::Map.read_json(path)
|
|
88
86
|
json.is_a?(Hash) ? json : nil
|
|
89
87
|
rescue JSON::ParserError
|
|
90
88
|
nil
|