Flask-Node 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastian Salinas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ recursive-include tests *.py
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: Flask-Node
3
+ Version: 0.1.0
4
+ Summary: Isolated Node/npm infrastructure for Flask extensions
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: Flask<4,>=2.2
10
+ Provides-Extra: test
11
+ Requires-Dist: pytest>=7; extra == "test"
12
+ Dynamic: license-file
13
+
14
+ # Flask-Node
15
+
16
+ Flask-Node manages an isolated Node/npm project for Flask applications and
17
+ extensions. It contains no JavaScript library integrations, bundler, asset
18
+ server, or frontend framework.
19
+
20
+ Requires Python 3.10+ and Flask 2.2–3.x. Install Node.js (including npm/npx)
21
+ separately when executing commands; importing and initializing the Flask
22
+ extension does not require it.
23
+
24
+ ```bash
25
+ pip install Flask-Node
26
+ ```
27
+
28
+ ## Application setup
29
+
30
+ ```python
31
+ from flask import Flask
32
+ from flask_node import Node
33
+
34
+ node = Node()
35
+
36
+ def create_app():
37
+ app = Flask(__name__)
38
+ node.init_app(app)
39
+ return app
40
+ ```
41
+
42
+ `Node(app)` is also supported. `init_app()` registers defaults and the CLI,
43
+ without creating directories or launching commands. Each application owns a
44
+ separate `NodeManager` in `app.extensions["node"]`; a shared `Node` facade uses
45
+ the current application context. `node.get_manager(app)` gives explicit access
46
+ outside a context. Duplicate registration raises `ConfigurationError`.
47
+
48
+ ## Configuration
49
+
50
+ | Setting | Default | Meaning |
51
+ | --- | --- | --- |
52
+ | `NODE_DIR` | `.node` | Relative to `app.root_path`, or an absolute path |
53
+ | `NODE_BIN` | `node` | Node executable name or path |
54
+ | `NODE_NPM_BIN` | `npm` | npm executable name or path |
55
+ | `NODE_NPX_BIN` | `npx` | npx executable name or path |
56
+
57
+ Configure before calling `init_app()`. Configuration is captured per app.
58
+ For a directory beside an application package, configure an absolute project
59
+ path. No behavior depends on the shell's current directory.
60
+
61
+ ## Lifecycle and dependencies
62
+
63
+ ```python
64
+ with app.app_context():
65
+ node.require("example", version="^1")
66
+ node.require("@scope/build-tool", version="^2", dev=True)
67
+ node.initialize()
68
+ node.install()
69
+ node.install("another-package", version="^3")
70
+ node.uninstall("another-package")
71
+ ```
72
+
73
+ `require()` only records an in-memory declaration. Consumer extensions may call
74
+ it during application setup. It creates no files and does not check or install
75
+ packages. Identical declarations are idempotent; differing versions or dependency
76
+ sections raise `DependencyConflictError`. Version declarations are compared as
77
+ strings; Flask-Node does not solve semver ranges.
78
+
79
+ `initialize()` creates `.node/package.json` with `private: true` and empty
80
+ `dependencies` / `devDependencies`. It preserves an existing valid manifest
81
+ without rewriting it. Invalid manifests fail clearly. npm creates the lockfile
82
+ and installed modules later:
83
+
84
+ ```text
85
+ .node/
86
+ package.json
87
+ package-lock.json
88
+ node_modules/
89
+ ```
90
+
91
+ `install()` initializes if necessary, merges active declarations into the
92
+ manifest, preserves unrelated fields and dependencies, and runs `npm install`.
93
+ Declarations take precedence over persisted entries for the same package.
94
+ `install(name, version=None, dev=False)` additionally installs a registry package;
95
+ without a version, npm chooses it unless an active declaration supplies one.
96
+ Conflicting explicit options fail before installation. Uninstalling an actively
97
+ required package is rejected. npm failures may leave manifest changes or partial
98
+ installation artifacts; operations are not transactional.
99
+
100
+ `ci()` requires `package-lock.json` and declarations matching the manifest, then
101
+ runs `npm ci` without rewriting the manifest. npm validates lockfile consistency.
102
+ Commit `.node/package.json` and `.node/package-lock.json` for reproducible builds;
103
+ ignore `.node/node_modules/`.
104
+
105
+ ## Commands and diagnostics
106
+
107
+ ```python
108
+ with app.app_context():
109
+ result = node.npm("run", "custom-script")
110
+ result = node.npx("some-tool", "--help", capture_output=False)
111
+ print(node.status())
112
+ ```
113
+
114
+ Raw commands require an initialized environment and run with its directory as
115
+ `cwd`. Arguments are passed individually with `shell=False`. Python calls capture
116
+ output by default; use `capture_output=False` to inherit terminal streams.
117
+ `CommandResult` exposes `args`, `returncode`, `stdout`, and `stderr`.
118
+
119
+ `status()` reports the directory, manifest presence, and executable locations
120
+ (or `None`). It launches no processes and does not validate executable versions.
121
+ Missing executables raise `ExecutableNotFoundError`; nonzero exits and other
122
+ launch failures raise `CommandExecutionError`, which retains `command`, `cwd`,
123
+ `returncode`, `stdout`, and `stderr`. All public errors derive from `NodeError`.
124
+
125
+ npm/npx may access the network and run package scripts. The managed working
126
+ directory is not a process sandbox; invoked tools can write elsewhere.
127
+
128
+ ## Flask CLI
129
+
130
+ ```bash
131
+ flask --app your_app node init
132
+ flask --app your_app node install
133
+ flask --app your_app node install example --version '^1' --dev
134
+ flask --app your_app node uninstall example
135
+ flask --app your_app node npm -- run custom-script --flag
136
+ flask --app your_app node npx -- some-tool --help
137
+ flask --app your_app node ci
138
+ flask --app your_app node status
139
+ ```
140
+
141
+ CLI commands use the same Python operations, stream subprocess output, and
142
+ report extension errors with a nonzero exit status. `--` separates forwarding
143
+ arguments from Click's own options.
144
+
145
+ ## Installed assets and extension consumers
146
+
147
+ ```python
148
+ manager = app.extensions["node"]
149
+ manager.require("example", "^1") # Safe during consumer init_app().
150
+
151
+ # After an explicit installation step:
152
+ asset = manager.resolve("example", "dist/example.js")
153
+ package = manager.package("example")
154
+ assert package.resolve("dist/example.js") == asset
155
+ print(package.name, package.version, package.root)
156
+ ```
157
+
158
+ The consumer must initialize Flask-Node first. It owns its library-specific
159
+ configuration, rendering, asset copying/serving, and build/watch commands.
160
+ Flask-Node only declares/installs dependencies, executes commands, and locates
161
+ files. No registration protocol is needed.
162
+
163
+ Package lookup supports ordinary and scoped registry names. Asset lookup returns
164
+ an existing `Path`; absolute paths, parent traversal, Windows-style paths, and
165
+ symlinks escaping the package or managed environment are rejected. Externally
166
+ linked packages are intentionally unsupported. Missing or malformed installed
167
+ packages raise `PackageNotFoundError`; unsafe or missing assets raise
168
+ `AssetResolutionError`. This is filesystem path validation, not protection
169
+ against concurrent malicious filesystem changes.
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ python -m venv .venv
175
+ .venv/bin/python -m pip install -e '.[test]' build
176
+ .venv/bin/python -m pytest
177
+ .venv/bin/python -m build
178
+ ```
179
+
180
+ Tests inject/mock the runner and subprocess boundary. They never download npm
181
+ packages or require Node. A custom runner can be supplied to `Node(runner=...)`
182
+ or `NodeManager(..., runner=...)` for testing. This dependency injection is not a
183
+ consumer plugin protocol.
184
+
185
+ Inspired by [Flask-Tailwind-Manager](https://github.com/SebaSalinass/flask-tailwind-manager).
@@ -0,0 +1,172 @@
1
+ # Flask-Node
2
+
3
+ Flask-Node manages an isolated Node/npm project for Flask applications and
4
+ extensions. It contains no JavaScript library integrations, bundler, asset
5
+ server, or frontend framework.
6
+
7
+ Requires Python 3.10+ and Flask 2.2–3.x. Install Node.js (including npm/npx)
8
+ separately when executing commands; importing and initializing the Flask
9
+ extension does not require it.
10
+
11
+ ```bash
12
+ pip install Flask-Node
13
+ ```
14
+
15
+ ## Application setup
16
+
17
+ ```python
18
+ from flask import Flask
19
+ from flask_node import Node
20
+
21
+ node = Node()
22
+
23
+ def create_app():
24
+ app = Flask(__name__)
25
+ node.init_app(app)
26
+ return app
27
+ ```
28
+
29
+ `Node(app)` is also supported. `init_app()` registers defaults and the CLI,
30
+ without creating directories or launching commands. Each application owns a
31
+ separate `NodeManager` in `app.extensions["node"]`; a shared `Node` facade uses
32
+ the current application context. `node.get_manager(app)` gives explicit access
33
+ outside a context. Duplicate registration raises `ConfigurationError`.
34
+
35
+ ## Configuration
36
+
37
+ | Setting | Default | Meaning |
38
+ | --- | --- | --- |
39
+ | `NODE_DIR` | `.node` | Relative to `app.root_path`, or an absolute path |
40
+ | `NODE_BIN` | `node` | Node executable name or path |
41
+ | `NODE_NPM_BIN` | `npm` | npm executable name or path |
42
+ | `NODE_NPX_BIN` | `npx` | npx executable name or path |
43
+
44
+ Configure before calling `init_app()`. Configuration is captured per app.
45
+ For a directory beside an application package, configure an absolute project
46
+ path. No behavior depends on the shell's current directory.
47
+
48
+ ## Lifecycle and dependencies
49
+
50
+ ```python
51
+ with app.app_context():
52
+ node.require("example", version="^1")
53
+ node.require("@scope/build-tool", version="^2", dev=True)
54
+ node.initialize()
55
+ node.install()
56
+ node.install("another-package", version="^3")
57
+ node.uninstall("another-package")
58
+ ```
59
+
60
+ `require()` only records an in-memory declaration. Consumer extensions may call
61
+ it during application setup. It creates no files and does not check or install
62
+ packages. Identical declarations are idempotent; differing versions or dependency
63
+ sections raise `DependencyConflictError`. Version declarations are compared as
64
+ strings; Flask-Node does not solve semver ranges.
65
+
66
+ `initialize()` creates `.node/package.json` with `private: true` and empty
67
+ `dependencies` / `devDependencies`. It preserves an existing valid manifest
68
+ without rewriting it. Invalid manifests fail clearly. npm creates the lockfile
69
+ and installed modules later:
70
+
71
+ ```text
72
+ .node/
73
+ package.json
74
+ package-lock.json
75
+ node_modules/
76
+ ```
77
+
78
+ `install()` initializes if necessary, merges active declarations into the
79
+ manifest, preserves unrelated fields and dependencies, and runs `npm install`.
80
+ Declarations take precedence over persisted entries for the same package.
81
+ `install(name, version=None, dev=False)` additionally installs a registry package;
82
+ without a version, npm chooses it unless an active declaration supplies one.
83
+ Conflicting explicit options fail before installation. Uninstalling an actively
84
+ required package is rejected. npm failures may leave manifest changes or partial
85
+ installation artifacts; operations are not transactional.
86
+
87
+ `ci()` requires `package-lock.json` and declarations matching the manifest, then
88
+ runs `npm ci` without rewriting the manifest. npm validates lockfile consistency.
89
+ Commit `.node/package.json` and `.node/package-lock.json` for reproducible builds;
90
+ ignore `.node/node_modules/`.
91
+
92
+ ## Commands and diagnostics
93
+
94
+ ```python
95
+ with app.app_context():
96
+ result = node.npm("run", "custom-script")
97
+ result = node.npx("some-tool", "--help", capture_output=False)
98
+ print(node.status())
99
+ ```
100
+
101
+ Raw commands require an initialized environment and run with its directory as
102
+ `cwd`. Arguments are passed individually with `shell=False`. Python calls capture
103
+ output by default; use `capture_output=False` to inherit terminal streams.
104
+ `CommandResult` exposes `args`, `returncode`, `stdout`, and `stderr`.
105
+
106
+ `status()` reports the directory, manifest presence, and executable locations
107
+ (or `None`). It launches no processes and does not validate executable versions.
108
+ Missing executables raise `ExecutableNotFoundError`; nonzero exits and other
109
+ launch failures raise `CommandExecutionError`, which retains `command`, `cwd`,
110
+ `returncode`, `stdout`, and `stderr`. All public errors derive from `NodeError`.
111
+
112
+ npm/npx may access the network and run package scripts. The managed working
113
+ directory is not a process sandbox; invoked tools can write elsewhere.
114
+
115
+ ## Flask CLI
116
+
117
+ ```bash
118
+ flask --app your_app node init
119
+ flask --app your_app node install
120
+ flask --app your_app node install example --version '^1' --dev
121
+ flask --app your_app node uninstall example
122
+ flask --app your_app node npm -- run custom-script --flag
123
+ flask --app your_app node npx -- some-tool --help
124
+ flask --app your_app node ci
125
+ flask --app your_app node status
126
+ ```
127
+
128
+ CLI commands use the same Python operations, stream subprocess output, and
129
+ report extension errors with a nonzero exit status. `--` separates forwarding
130
+ arguments from Click's own options.
131
+
132
+ ## Installed assets and extension consumers
133
+
134
+ ```python
135
+ manager = app.extensions["node"]
136
+ manager.require("example", "^1") # Safe during consumer init_app().
137
+
138
+ # After an explicit installation step:
139
+ asset = manager.resolve("example", "dist/example.js")
140
+ package = manager.package("example")
141
+ assert package.resolve("dist/example.js") == asset
142
+ print(package.name, package.version, package.root)
143
+ ```
144
+
145
+ The consumer must initialize Flask-Node first. It owns its library-specific
146
+ configuration, rendering, asset copying/serving, and build/watch commands.
147
+ Flask-Node only declares/installs dependencies, executes commands, and locates
148
+ files. No registration protocol is needed.
149
+
150
+ Package lookup supports ordinary and scoped registry names. Asset lookup returns
151
+ an existing `Path`; absolute paths, parent traversal, Windows-style paths, and
152
+ symlinks escaping the package or managed environment are rejected. Externally
153
+ linked packages are intentionally unsupported. Missing or malformed installed
154
+ packages raise `PackageNotFoundError`; unsafe or missing assets raise
155
+ `AssetResolutionError`. This is filesystem path validation, not protection
156
+ against concurrent malicious filesystem changes.
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ python -m venv .venv
162
+ .venv/bin/python -m pip install -e '.[test]' build
163
+ .venv/bin/python -m pytest
164
+ .venv/bin/python -m build
165
+ ```
166
+
167
+ Tests inject/mock the runner and subprocess boundary. They never download npm
168
+ packages or require Node. A custom runner can be supplied to `Node(runner=...)`
169
+ or `NodeManager(..., runner=...)` for testing. This dependency injection is not a
170
+ consumer plugin protocol.
171
+
172
+ Inspired by [Flask-Tailwind-Manager](https://github.com/SebaSalinass/flask-tailwind-manager).
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "Flask-Node"
7
+ version = "0.1.0"
8
+ description = "Isolated Node/npm infrastructure for Flask extensions"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = ["Flask>=2.2,<4"]
14
+
15
+ [project.optional-dependencies]
16
+ test = ["pytest>=7"]
17
+
18
+ [tool.setuptools.packages.find]
19
+ where = ["src"]
20
+
21
+ [tool.setuptools.package-data]
22
+ flask_node = ["py.typed"]
23
+
24
+ [tool.pytest.ini_options]
25
+ testpaths = ["tests"]
26
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: Flask-Node
3
+ Version: 0.1.0
4
+ Summary: Isolated Node/npm infrastructure for Flask extensions
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: Flask<4,>=2.2
10
+ Provides-Extra: test
11
+ Requires-Dist: pytest>=7; extra == "test"
12
+ Dynamic: license-file
13
+
14
+ # Flask-Node
15
+
16
+ Flask-Node manages an isolated Node/npm project for Flask applications and
17
+ extensions. It contains no JavaScript library integrations, bundler, asset
18
+ server, or frontend framework.
19
+
20
+ Requires Python 3.10+ and Flask 2.2–3.x. Install Node.js (including npm/npx)
21
+ separately when executing commands; importing and initializing the Flask
22
+ extension does not require it.
23
+
24
+ ```bash
25
+ pip install Flask-Node
26
+ ```
27
+
28
+ ## Application setup
29
+
30
+ ```python
31
+ from flask import Flask
32
+ from flask_node import Node
33
+
34
+ node = Node()
35
+
36
+ def create_app():
37
+ app = Flask(__name__)
38
+ node.init_app(app)
39
+ return app
40
+ ```
41
+
42
+ `Node(app)` is also supported. `init_app()` registers defaults and the CLI,
43
+ without creating directories or launching commands. Each application owns a
44
+ separate `NodeManager` in `app.extensions["node"]`; a shared `Node` facade uses
45
+ the current application context. `node.get_manager(app)` gives explicit access
46
+ outside a context. Duplicate registration raises `ConfigurationError`.
47
+
48
+ ## Configuration
49
+
50
+ | Setting | Default | Meaning |
51
+ | --- | --- | --- |
52
+ | `NODE_DIR` | `.node` | Relative to `app.root_path`, or an absolute path |
53
+ | `NODE_BIN` | `node` | Node executable name or path |
54
+ | `NODE_NPM_BIN` | `npm` | npm executable name or path |
55
+ | `NODE_NPX_BIN` | `npx` | npx executable name or path |
56
+
57
+ Configure before calling `init_app()`. Configuration is captured per app.
58
+ For a directory beside an application package, configure an absolute project
59
+ path. No behavior depends on the shell's current directory.
60
+
61
+ ## Lifecycle and dependencies
62
+
63
+ ```python
64
+ with app.app_context():
65
+ node.require("example", version="^1")
66
+ node.require("@scope/build-tool", version="^2", dev=True)
67
+ node.initialize()
68
+ node.install()
69
+ node.install("another-package", version="^3")
70
+ node.uninstall("another-package")
71
+ ```
72
+
73
+ `require()` only records an in-memory declaration. Consumer extensions may call
74
+ it during application setup. It creates no files and does not check or install
75
+ packages. Identical declarations are idempotent; differing versions or dependency
76
+ sections raise `DependencyConflictError`. Version declarations are compared as
77
+ strings; Flask-Node does not solve semver ranges.
78
+
79
+ `initialize()` creates `.node/package.json` with `private: true` and empty
80
+ `dependencies` / `devDependencies`. It preserves an existing valid manifest
81
+ without rewriting it. Invalid manifests fail clearly. npm creates the lockfile
82
+ and installed modules later:
83
+
84
+ ```text
85
+ .node/
86
+ package.json
87
+ package-lock.json
88
+ node_modules/
89
+ ```
90
+
91
+ `install()` initializes if necessary, merges active declarations into the
92
+ manifest, preserves unrelated fields and dependencies, and runs `npm install`.
93
+ Declarations take precedence over persisted entries for the same package.
94
+ `install(name, version=None, dev=False)` additionally installs a registry package;
95
+ without a version, npm chooses it unless an active declaration supplies one.
96
+ Conflicting explicit options fail before installation. Uninstalling an actively
97
+ required package is rejected. npm failures may leave manifest changes or partial
98
+ installation artifacts; operations are not transactional.
99
+
100
+ `ci()` requires `package-lock.json` and declarations matching the manifest, then
101
+ runs `npm ci` without rewriting the manifest. npm validates lockfile consistency.
102
+ Commit `.node/package.json` and `.node/package-lock.json` for reproducible builds;
103
+ ignore `.node/node_modules/`.
104
+
105
+ ## Commands and diagnostics
106
+
107
+ ```python
108
+ with app.app_context():
109
+ result = node.npm("run", "custom-script")
110
+ result = node.npx("some-tool", "--help", capture_output=False)
111
+ print(node.status())
112
+ ```
113
+
114
+ Raw commands require an initialized environment and run with its directory as
115
+ `cwd`. Arguments are passed individually with `shell=False`. Python calls capture
116
+ output by default; use `capture_output=False` to inherit terminal streams.
117
+ `CommandResult` exposes `args`, `returncode`, `stdout`, and `stderr`.
118
+
119
+ `status()` reports the directory, manifest presence, and executable locations
120
+ (or `None`). It launches no processes and does not validate executable versions.
121
+ Missing executables raise `ExecutableNotFoundError`; nonzero exits and other
122
+ launch failures raise `CommandExecutionError`, which retains `command`, `cwd`,
123
+ `returncode`, `stdout`, and `stderr`. All public errors derive from `NodeError`.
124
+
125
+ npm/npx may access the network and run package scripts. The managed working
126
+ directory is not a process sandbox; invoked tools can write elsewhere.
127
+
128
+ ## Flask CLI
129
+
130
+ ```bash
131
+ flask --app your_app node init
132
+ flask --app your_app node install
133
+ flask --app your_app node install example --version '^1' --dev
134
+ flask --app your_app node uninstall example
135
+ flask --app your_app node npm -- run custom-script --flag
136
+ flask --app your_app node npx -- some-tool --help
137
+ flask --app your_app node ci
138
+ flask --app your_app node status
139
+ ```
140
+
141
+ CLI commands use the same Python operations, stream subprocess output, and
142
+ report extension errors with a nonzero exit status. `--` separates forwarding
143
+ arguments from Click's own options.
144
+
145
+ ## Installed assets and extension consumers
146
+
147
+ ```python
148
+ manager = app.extensions["node"]
149
+ manager.require("example", "^1") # Safe during consumer init_app().
150
+
151
+ # After an explicit installation step:
152
+ asset = manager.resolve("example", "dist/example.js")
153
+ package = manager.package("example")
154
+ assert package.resolve("dist/example.js") == asset
155
+ print(package.name, package.version, package.root)
156
+ ```
157
+
158
+ The consumer must initialize Flask-Node first. It owns its library-specific
159
+ configuration, rendering, asset copying/serving, and build/watch commands.
160
+ Flask-Node only declares/installs dependencies, executes commands, and locates
161
+ files. No registration protocol is needed.
162
+
163
+ Package lookup supports ordinary and scoped registry names. Asset lookup returns
164
+ an existing `Path`; absolute paths, parent traversal, Windows-style paths, and
165
+ symlinks escaping the package or managed environment are rejected. Externally
166
+ linked packages are intentionally unsupported. Missing or malformed installed
167
+ packages raise `PackageNotFoundError`; unsafe or missing assets raise
168
+ `AssetResolutionError`. This is filesystem path validation, not protection
169
+ against concurrent malicious filesystem changes.
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ python -m venv .venv
175
+ .venv/bin/python -m pip install -e '.[test]' build
176
+ .venv/bin/python -m pytest
177
+ .venv/bin/python -m build
178
+ ```
179
+
180
+ Tests inject/mock the runner and subprocess boundary. They never download npm
181
+ packages or require Node. A custom runner can be supplied to `Node(runner=...)`
182
+ or `NodeManager(..., runner=...)` for testing. This dependency injection is not a
183
+ consumer plugin protocol.
184
+
185
+ Inspired by [Flask-Tailwind-Manager](https://github.com/SebaSalinass/flask-tailwind-manager).
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ src/Flask_Node.egg-info/PKG-INFO
6
+ src/Flask_Node.egg-info/SOURCES.txt
7
+ src/Flask_Node.egg-info/dependency_links.txt
8
+ src/Flask_Node.egg-info/requires.txt
9
+ src/Flask_Node.egg-info/top_level.txt
10
+ src/flask_node/__init__.py
11
+ src/flask_node/cli.py
12
+ src/flask_node/exceptions.py
13
+ src/flask_node/extension.py
14
+ src/flask_node/manager.py
15
+ src/flask_node/package.py
16
+ src/flask_node/py.typed
17
+ src/flask_node/runner.py
18
+ tests/conftest.py
19
+ tests/test_cli.py
20
+ tests/test_extension.py
21
+ tests/test_manager.py
22
+ tests/test_package.py
23
+ tests/test_runner.py
@@ -0,0 +1,4 @@
1
+ Flask<4,>=2.2
2
+
3
+ [test]
4
+ pytest>=7
@@ -0,0 +1 @@
1
+ flask_node
@@ -0,0 +1,15 @@
1
+ """Generic Node/npm infrastructure for Flask applications."""
2
+ from .extension import Node
3
+ from .manager import NodeManager
4
+ from .package import Package
5
+ from .runner import CommandResult, CommandRunner
6
+ from .exceptions import (
7
+ NodeError, ConfigurationError, EnvironmentError, ExecutableNotFoundError,
8
+ CommandExecutionError, DependencyConflictError, PackageNotFoundError,
9
+ AssetResolutionError,
10
+ )
11
+
12
+ __all__ = ["Node", "NodeManager", "Package", "CommandResult", "CommandRunner", "NodeError",
13
+ "ConfigurationError", "EnvironmentError", "ExecutableNotFoundError",
14
+ "CommandExecutionError", "DependencyConflictError", "PackageNotFoundError",
15
+ "AssetResolutionError"]