hatch-js 0.1.0__py3-none-any.whl
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.
- hatch_js/__init__.py +5 -0
- hatch_js/hooks.py +10 -0
- hatch_js/plugin.py +110 -0
- hatch_js/structs.py +103 -0
- hatch_js/tests/test_project_basic/js/build.mjs +91 -0
- hatch_js/tests/test_project_basic/js/package.json +43 -0
- hatch_js/tests/test_project_basic/js/pnpm-lock.yaml +1979 -0
- hatch_js/tests/test_project_basic/js/src/html/index.html +13 -0
- hatch_js/tests/test_project_basic/js/src/js/index.js +0 -0
- hatch_js/tests/test_project_basic/js/src/less/index.less +0 -0
- hatch_js/tests/test_project_basic/project/__init__.py +0 -0
- hatch_js/tests/test_project_basic/pyproject.toml +36 -0
- hatch_js/tests/test_projects.py +33 -0
- hatch_js/utils.py +12 -0
- hatch_js-0.1.0.dist-info/METADATA +84 -0
- hatch_js-0.1.0.dist-info/RECORD +19 -0
- hatch_js-0.1.0.dist-info/WHEEL +4 -0
- hatch_js-0.1.0.dist-info/entry_points.txt +2 -0
- hatch_js-0.1.0.dist-info/licenses/LICENSE +201 -0
hatch_js/__init__.py
ADDED
hatch_js/hooks.py
ADDED
hatch_js/plugin.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from os import getenv
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
8
|
+
|
|
9
|
+
from .structs import HatchJsBuildConfig, HatchJsBuildPlan
|
|
10
|
+
from .utils import import_string
|
|
11
|
+
|
|
12
|
+
__all__ = ("HatchJsBuildHook",)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HatchJsBuildHook(BuildHookInterface[HatchJsBuildConfig]):
|
|
16
|
+
"""The hatch-js build hook."""
|
|
17
|
+
|
|
18
|
+
PLUGIN_NAME = "hatch-js"
|
|
19
|
+
_logger = getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
22
|
+
"""Initialize the plugin."""
|
|
23
|
+
# Log some basic information
|
|
24
|
+
project_name = self.metadata.config["project"]["name"]
|
|
25
|
+
self._logger.info("Initializing hatch-js plugin version %s", version)
|
|
26
|
+
self._logger.info(f"Running hatch-js: {project_name}")
|
|
27
|
+
|
|
28
|
+
# Only run if creating wheel
|
|
29
|
+
# TODO: Add support for specify sdist-plan
|
|
30
|
+
if self.target_name != "wheel":
|
|
31
|
+
self._logger.info("ignoring target name %s", self.target_name)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
# Skip if SKIP_HATCH_JS is set
|
|
35
|
+
# TODO: Support CLI once https://github.com/pypa/hatch/pull/1743
|
|
36
|
+
if getenv("SKIP_HATCH_JS"):
|
|
37
|
+
self._logger.info("Skipping the build hook since SKIP_HATCH_JS was set")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
# Get build config class or use default
|
|
41
|
+
build_config_class = import_string(self.config["build-config-class"]) if "build-config-class" in self.config else HatchJsBuildConfig
|
|
42
|
+
|
|
43
|
+
# Instantiate build config
|
|
44
|
+
config = build_config_class(name=project_name, **self.config)
|
|
45
|
+
|
|
46
|
+
# Get build plan class or use default
|
|
47
|
+
build_plan_class = import_string(self.config["build-plan-class"]) if "build-plan-class" in self.config else HatchJsBuildPlan
|
|
48
|
+
|
|
49
|
+
# Instantiate builder
|
|
50
|
+
build_plan = build_plan_class(**config.model_dump())
|
|
51
|
+
|
|
52
|
+
# Generate commands
|
|
53
|
+
build_plan.generate()
|
|
54
|
+
|
|
55
|
+
# Log commands if in verbose mode
|
|
56
|
+
if config.verbose:
|
|
57
|
+
for command in build_plan.commands:
|
|
58
|
+
self._logger.warning(command)
|
|
59
|
+
|
|
60
|
+
# Execute build plan
|
|
61
|
+
build_plan.execute()
|
|
62
|
+
|
|
63
|
+
# Perform any cleanup actions
|
|
64
|
+
build_plan.cleanup()
|
|
65
|
+
|
|
66
|
+
# if build_plan.libraries:
|
|
67
|
+
# # force include libraries
|
|
68
|
+
# for library in build_plan.libraries:
|
|
69
|
+
# name = library.get_qualified_name(build_plan.platform.platform)
|
|
70
|
+
# build_data["force_include"][name] = name
|
|
71
|
+
|
|
72
|
+
# build_data["pure_python"] = False
|
|
73
|
+
# machine = platform_machine()
|
|
74
|
+
# version_major = version_info.major
|
|
75
|
+
# version_minor = version_info.minor
|
|
76
|
+
# if "darwin" in sys_platform:
|
|
77
|
+
# os_name = "macosx_11_0"
|
|
78
|
+
# elif "linux" in sys_platform:
|
|
79
|
+
# os_name = "linux"
|
|
80
|
+
# else:
|
|
81
|
+
# os_name = "win"
|
|
82
|
+
# if all([lib.py_limited_api for lib in build_plan.libraries]):
|
|
83
|
+
# build_data["tag"] = f"cp{version_major}{version_minor}-abi3-{os_name}_{machine}"
|
|
84
|
+
# else:
|
|
85
|
+
# build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}"
|
|
86
|
+
# else:
|
|
87
|
+
# build_data["pure_python"] = False
|
|
88
|
+
# machine = platform_machine()
|
|
89
|
+
# version_major = version_info.major
|
|
90
|
+
# version_minor = version_info.minor
|
|
91
|
+
# # TODO abi3
|
|
92
|
+
# if "darwin" in sys_platform:
|
|
93
|
+
# os_name = "macosx_11_0"
|
|
94
|
+
# elif "linux" in sys_platform:
|
|
95
|
+
# os_name = "linux"
|
|
96
|
+
# else:
|
|
97
|
+
# os_name = "win"
|
|
98
|
+
# build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}"
|
|
99
|
+
|
|
100
|
+
# # force include libraries
|
|
101
|
+
# for path in Path(".").rglob("*"):
|
|
102
|
+
# if path.is_dir():
|
|
103
|
+
# continue
|
|
104
|
+
# if str(path).startswith(str(build_plan.cmake.build)) or str(path).startswith("dist"):
|
|
105
|
+
# continue
|
|
106
|
+
# if path.suffix in (".pyd", ".dll", ".so", ".dylib"):
|
|
107
|
+
# build_data["force_include"][str(path)] = str(path)
|
|
108
|
+
|
|
109
|
+
# for path in build_data["force_include"]:
|
|
110
|
+
# self._logger.warning(f"Force include: {path}")
|
hatch_js/structs.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from os import chdir, curdir, system as system_call
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from shutil import which
|
|
6
|
+
from typing import List, Literal, Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, field_validator
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
"HatchJsBuildConfig",
|
|
12
|
+
"HatchJsBuildPlan",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
Toolchain = Literal["npm", "yarn", "pnpm", "jlpm"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HatchJsBuildConfig(BaseModel):
|
|
19
|
+
"""Build config values for Hatch Js Builder."""
|
|
20
|
+
|
|
21
|
+
name: Optional[str] = Field(default=None)
|
|
22
|
+
verbose: Optional[bool] = Field(default=False)
|
|
23
|
+
|
|
24
|
+
path: Optional[Path] = Field(default=None, description="Path to the JavaScript project. Defaults to the current directory.")
|
|
25
|
+
tool: Optional[Toolchain] = Field(default="npm", description="Command to run for building the project, e.g., 'npm', 'yarn', 'pnpm'")
|
|
26
|
+
|
|
27
|
+
install_cmd: Optional[str] = Field(
|
|
28
|
+
default=None, description="Custom command to run for installing dependencies. If specified, overrides the default install command."
|
|
29
|
+
)
|
|
30
|
+
build_cmd: Optional[str] = Field(
|
|
31
|
+
default="build", description="Custom command to run for building the project. If specified, overrides the default build command."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
targets: Optional[List[str]] = Field(default_factory=list, description="List of ensured targets to build")
|
|
35
|
+
|
|
36
|
+
# Check that tool exists
|
|
37
|
+
@field_validator("tool", mode="before")
|
|
38
|
+
@classmethod
|
|
39
|
+
def _check_tool_exists(cls, tool: Toolchain) -> Toolchain:
|
|
40
|
+
if not which(tool):
|
|
41
|
+
raise ValueError(f"Tool '{tool}' not found in PATH. Please install it or specify a different tool.")
|
|
42
|
+
return tool
|
|
43
|
+
|
|
44
|
+
# Validate path
|
|
45
|
+
@field_validator("path", mode="before")
|
|
46
|
+
@classmethod
|
|
47
|
+
def validate_path(cls, path: Optional[Path]) -> Path:
|
|
48
|
+
if path is None:
|
|
49
|
+
return Path.cwd()
|
|
50
|
+
if not isinstance(path, Path):
|
|
51
|
+
path = Path(path)
|
|
52
|
+
if not path.is_dir():
|
|
53
|
+
raise ValueError(f"Path '{path}' is not a valid directory.")
|
|
54
|
+
return path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class HatchJsBuildPlan(HatchJsBuildConfig):
|
|
58
|
+
commands: List[str] = Field(default_factory=list)
|
|
59
|
+
|
|
60
|
+
def generate(self):
|
|
61
|
+
self.commands = []
|
|
62
|
+
|
|
63
|
+
# Run installation
|
|
64
|
+
if self.tool in ("npm", "pnpm"):
|
|
65
|
+
if self.install_cmd:
|
|
66
|
+
self.commands.append(f"{self.tool} {self.install_cmd}")
|
|
67
|
+
else:
|
|
68
|
+
self.commands.append(f"{self.tool} install")
|
|
69
|
+
elif self.tool in ("yarn", "jlpm"):
|
|
70
|
+
if self.install_cmd:
|
|
71
|
+
self.commands.append(f"{self.tool} {self.install_cmd}")
|
|
72
|
+
else:
|
|
73
|
+
self.commands.append(f"{self.tool}")
|
|
74
|
+
|
|
75
|
+
# Run build command
|
|
76
|
+
if self.tool in ("npm", "pnpm"):
|
|
77
|
+
self.commands.append(f"{self.tool} run {self.build_cmd}")
|
|
78
|
+
elif self.tool in ("yarn", "jlpm"):
|
|
79
|
+
self.commands.append(f"{self.tool} {self.build_cmd}")
|
|
80
|
+
|
|
81
|
+
return self.commands
|
|
82
|
+
|
|
83
|
+
def execute(self):
|
|
84
|
+
"""Execute the build commands."""
|
|
85
|
+
|
|
86
|
+
# First navigate to the project path
|
|
87
|
+
cwd = Path(curdir).resolve()
|
|
88
|
+
chdir(self.path)
|
|
89
|
+
|
|
90
|
+
for command in self.commands:
|
|
91
|
+
system_call(command)
|
|
92
|
+
|
|
93
|
+
# Check that all targets exist
|
|
94
|
+
# Go back to original path
|
|
95
|
+
chdir(str(cwd))
|
|
96
|
+
for target in self.targets:
|
|
97
|
+
if not Path(target).resolve().exists():
|
|
98
|
+
raise FileNotFoundError(f"Target '{target}' does not exist after build. Please check your build configuration.")
|
|
99
|
+
return self.commands
|
|
100
|
+
|
|
101
|
+
def cleanup(self):
|
|
102
|
+
# No-op
|
|
103
|
+
...
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { NodeModulesExternal } from "@finos/perspective-esbuild-plugin/external.js";
|
|
2
|
+
import { build } from "@finos/perspective-esbuild-plugin/build.js";
|
|
3
|
+
import { BuildCss } from "@prospective.co/procss/target/cjs/procss.js";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import cpy from "cpy";
|
|
6
|
+
import path_mod from "path";
|
|
7
|
+
|
|
8
|
+
const COMMON_DEFINE = {
|
|
9
|
+
global: "window",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const BUILD = [
|
|
13
|
+
{
|
|
14
|
+
define: COMMON_DEFINE,
|
|
15
|
+
entryPoints: ["src/js/index.js"],
|
|
16
|
+
plugins: [NodeModulesExternal()],
|
|
17
|
+
format: "esm",
|
|
18
|
+
loader: {
|
|
19
|
+
".css": "text",
|
|
20
|
+
".html": "text",
|
|
21
|
+
},
|
|
22
|
+
outfile: "dist/esm/index.js",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
define: COMMON_DEFINE,
|
|
26
|
+
entryPoints: ["src/js/index.js"],
|
|
27
|
+
plugins: [],
|
|
28
|
+
format: "esm",
|
|
29
|
+
loader: {
|
|
30
|
+
".css": "text",
|
|
31
|
+
".html": "text",
|
|
32
|
+
},
|
|
33
|
+
outfile: "dist/cdn/index.js",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
async function compile_css() {
|
|
38
|
+
const process_path = (path) => {
|
|
39
|
+
const outpath = path.replace("src/less", "dist/css");
|
|
40
|
+
fs.mkdirSync(outpath, { recursive: true });
|
|
41
|
+
|
|
42
|
+
fs.readdirSync(path).forEach((file_or_folder) => {
|
|
43
|
+
if (file_or_folder.endsWith(".less")) {
|
|
44
|
+
const outfile = file_or_folder.replace(".less", ".css");
|
|
45
|
+
const builder = new BuildCss("");
|
|
46
|
+
builder.add(
|
|
47
|
+
`${path}/${file_or_folder}`,
|
|
48
|
+
fs
|
|
49
|
+
.readFileSync(path_mod.join(`${path}/${file_or_folder}`))
|
|
50
|
+
.toString(),
|
|
51
|
+
);
|
|
52
|
+
fs.writeFileSync(
|
|
53
|
+
`${path.replace("src/less", "dist/css")}/${outfile}`,
|
|
54
|
+
builder.compile().get(outfile),
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
process_path(`${path}/${file_or_folder}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
// recursively process all less files in src/less
|
|
62
|
+
process_path("src/less");
|
|
63
|
+
cpy("src/css/*", "dist/css/");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function copy_html() {
|
|
67
|
+
fs.mkdirSync("dist/html", { recursive: true });
|
|
68
|
+
cpy("src/html/*", "dist/html");
|
|
69
|
+
// also copy to top level
|
|
70
|
+
cpy("src/html/*", "dist/");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function copy_img() {
|
|
74
|
+
fs.mkdirSync("dist/img", { recursive: true });
|
|
75
|
+
cpy("src/img/*", "dist/img");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function copy_to_python() {
|
|
79
|
+
fs.mkdirSync("../project/extension", { recursive: true });
|
|
80
|
+
cpy("dist/**/*", "../project/extension");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function build_all() {
|
|
84
|
+
await compile_css();
|
|
85
|
+
await copy_html();
|
|
86
|
+
await copy_img();
|
|
87
|
+
await Promise.all(BUILD.map(build)).catch(() => process.exit(1));
|
|
88
|
+
await copy_to_python();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
build_all();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"repository": "git@github.com:python-project-templates/hatch-js.git",
|
|
6
|
+
"author": "hatch-js authors <3105306+timkpaine@users.noreply.github.com>",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"private": true,
|
|
9
|
+
"type": "module",
|
|
10
|
+
"unpkg": "dist/cdn/index.js",
|
|
11
|
+
"jsdelivr": "dist/cdn/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/esm/index.d.ts",
|
|
15
|
+
"default": "./dist/esm/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./dist/*": "./dist/*",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/**/*",
|
|
22
|
+
"index.d.ts"
|
|
23
|
+
],
|
|
24
|
+
"types": "./dist/esm/index.d.ts",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "node build.mjs",
|
|
30
|
+
"clean": "rm -rf dist playwright-report ../project/extension",
|
|
31
|
+
"preinstall": "npx only-allow pnpm",
|
|
32
|
+
"prepack": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@finos/perspective-esbuild-plugin": "^3.2.1",
|
|
37
|
+
"@prospective.co/procss": "^0.1.17",
|
|
38
|
+
"cpy": "^11.1.0",
|
|
39
|
+
"esbuild": "^0.25.5",
|
|
40
|
+
"esbuild-plugin-less": "^1.3.25",
|
|
41
|
+
"npm-run-all": "^4.1.5"
|
|
42
|
+
}
|
|
43
|
+
}
|