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 ADDED
@@ -0,0 +1,5 @@
1
+ __version__ = "0.1.0"
2
+
3
+ from .hooks import hatch_register_build_hook
4
+ from .plugin import HatchJsBuildHook
5
+ from .structs import *
hatch_js/hooks.py ADDED
@@ -0,0 +1,10 @@
1
+ from typing import Type
2
+
3
+ from hatchling.plugin import hookimpl
4
+
5
+ from .plugin import HatchJsBuildHook
6
+
7
+
8
+ @hookimpl
9
+ def hatch_register_build_hook() -> Type[HatchJsBuildHook]:
10
+ return HatchJsBuildHook
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
+ }