hdr-canvas 0.0.13 → 0.1.1
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.
- package/README.md +126 -29
- package/dist/@types/hdr-canvas.d.ts +63 -15
- package/dist/hdr-canvas.js +252 -102
- package/dist/hdr-canvas.js.map +1 -1
- package/dist/hdr-canvas.min.js +1 -1
- package/dist/hdr-canvas.min.js.map +1 -1
- package/dist/hdr-canvas.umd.js +6124 -5972
- package/dist/hdr-canvas.umd.js.map +1 -1
- package/dist/index.d.ts +63 -15
- package/docs/release-notes-0.1.0.md +47 -0
- package/docs/release-notes-0.1.1.md +19 -0
- package/docs/release.md +13 -0
- package/package.json +51 -16
- package/patches/@typescript+dom-lib-generator+0.0.1.patch +46 -0
- package/scripts/check.sh +12 -0
- package/scripts/git-submodules.js +175 -0
- package/src/Float16Image.ts +238 -0
- package/src/HDRImage.ts +130 -0
- package/src/Uint16Image.ts +86 -68
- package/src/browser-util.ts +9 -0
- package/src/hdr-canvas.ts +52 -8
- package/src/hdr-check.ts +31 -13
- package/src/index.ts +2 -0
- package/src/types/HDRCanvas.d.ts +34 -3
- package/src/types/ImageData.d.ts +17 -0
- package/three/HDRWebGPUBackend.js +45 -61
- package/three/HDRWebGPURenderer.js +19 -0
- package/tsconfig.json +12 -6
- package/dist/hdr-canvas.d.ts +0 -4
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { ColorTypes } from 'colorjs.io';
|
|
|
6
6
|
type HDRHTMLCanvasOptionsType = "mode";
|
|
7
7
|
type HDRHTMLCanvasOptions = { [key in HDRHTMLCanvasOptionsType]?: string };
|
|
8
8
|
|
|
9
|
+
type HDRImageDataArray = ImageDataArray | Float16Array | Uint16Array;
|
|
10
|
+
|
|
9
11
|
interface HDRHTMLCanvasElement extends HTMLCanvasElement {
|
|
10
12
|
configureHighDynamicRange(options: HDRHTMLCanvasOptions): void;
|
|
11
13
|
_getContext(contextId: string, options?: object): RenderingContext | null;
|
|
@@ -13,45 +15,91 @@ interface HDRHTMLCanvasElement extends HTMLCanvasElement {
|
|
|
13
15
|
|
|
14
16
|
interface HDRImageData {
|
|
15
17
|
readonly colorSpace: PredefinedColorSpace;
|
|
16
|
-
readonly data:
|
|
18
|
+
readonly data: HDRImageDataArray | Uint16Array;
|
|
17
19
|
readonly height: number;
|
|
18
20
|
readonly width: number;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
// See https://github.com/w3c/ColorWeb-CG/blob/main/hdr_html_canvas_element.md
|
|
22
|
-
// "rec2100-display-linear" is left out
|
|
24
|
+
// "rec2100-display-linear" is left out because of mapping issues
|
|
23
25
|
type HDRPredefinedColorSpace = "display-p3" | "srgb" | "rec2100-hlg" | "rec2100-pq";
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
//enum HDRPredefinedColorSpace {"display-p3", "srgb", "rec2100-hlg", "rec2100-pq", "rec2100-display-linear"};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A callback function that receives the red, green, blue, and alpha values of a pixel
|
|
31
|
+
* and returns a new `Float16Array` with the modified values.
|
|
32
|
+
*
|
|
33
|
+
* @callback HDRImagePixelCallback
|
|
34
|
+
* @param {number} red - The red channel value (0-65535).
|
|
35
|
+
* @param {number} green - The green channel value (0-65535).
|
|
36
|
+
* @param {number} blue - The blue channel value (0-65535).
|
|
37
|
+
* @param {number} alpha - The alpha channel value (0-65535).
|
|
38
|
+
* @returns {ImageDataArray} A new `Float16Array` containing the four channel values.
|
|
39
|
+
*/
|
|
40
|
+
type HDRImagePixelCallback = (red: number, green: number, blue: number, alpha: number) => HDRImageDataArray;
|
|
41
|
+
|
|
42
|
+
interface CanvasRenderingContext2DHDR extends CanvasRenderingContext2D {}
|
|
43
|
+
|
|
44
|
+
declare abstract class HDRImage {
|
|
30
45
|
static DEFAULT_COLORSPACE: HDRPredefinedColorSpace;
|
|
31
46
|
static SDR_MULTIPLIER: number;
|
|
32
47
|
static COLORSPACES: Record<HDRPredefinedColorSpace, ColorTypes>;
|
|
48
|
+
data: HDRImageDataArray;
|
|
49
|
+
height: number;
|
|
50
|
+
width: number;
|
|
51
|
+
constructor(width: number, height: number);
|
|
52
|
+
static fromImageData(imageData: HDRImageData | ImageData): HDRImage;
|
|
53
|
+
static fromImageDataArray(width: number, height: number, imageDataArray: Uint8ClampedArray | Uint8ClampedArray<ArrayBufferLike>): HDRImage;
|
|
54
|
+
static loadSDRImageData(url: URL): Promise<ImageData | undefined>;
|
|
55
|
+
getPixel(w: number, h: number): HDRImageDataArray;
|
|
56
|
+
setPixel(w: number, h: number, px: number[]): void;
|
|
57
|
+
abstract setImageData(imageData: HDRImageData | ImageData): void;
|
|
58
|
+
abstract getImageData(): ImageData | null;
|
|
59
|
+
abstract fill(color: number[]): this | undefined;
|
|
60
|
+
abstract pixelCallback(fn: HDRImagePixelCallback): void;
|
|
61
|
+
clone(): this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare class Uint16Image extends HDRImage {
|
|
65
|
+
data: Uint16Array;
|
|
33
66
|
colorSpace: HDRPredefinedColorSpace;
|
|
34
67
|
constructor(width: number, height: number, colorspace?: string);
|
|
35
|
-
fill(color: number[]):
|
|
36
|
-
getPixel(w: number, h: number): Uint16Array;
|
|
37
|
-
setPixel(w: number, h: number, px: number[]): void;
|
|
68
|
+
fill(color: number[]): this | undefined;
|
|
38
69
|
static scaleUint8ToUint16(val: number): number;
|
|
39
70
|
getImageData(): ImageData | null;
|
|
40
71
|
static convertPixelToRec2100_hlg(pixel: Uint8ClampedArray): Uint16Array;
|
|
41
72
|
static convertArrayToRec2100_hlg(data: Uint8ClampedArray): Uint16Array;
|
|
42
|
-
pixelCallback(fn:
|
|
43
|
-
static loadSDRImageData(url: URL): Promise<HDRImageData | undefined>;
|
|
73
|
+
pixelCallback(fn: HDRImagePixelCallback): void;
|
|
44
74
|
static fromImageData(imageData: HDRImageData): Uint16Image;
|
|
45
75
|
static fromURL(url: URL): Promise<Uint16Image | undefined>;
|
|
46
76
|
setImageData(imageData: HDRImageData): void;
|
|
47
|
-
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare class Float16Image extends HDRImage {
|
|
80
|
+
data: Float16Array;
|
|
81
|
+
static DEFAULT_PIXELFORMAT: ImageDataPixelFormat;
|
|
82
|
+
colorSpace: HDRPredefinedColorSpace;
|
|
83
|
+
pixelFormat: ImageDataPixelFormat;
|
|
84
|
+
constructor(width: number, height: number, colorspace?: string, pixelFormat?: string);
|
|
85
|
+
fill(color: number[]): this | undefined;
|
|
86
|
+
static scaleUint8ToFloat16(val: number): number;
|
|
87
|
+
getImageData(): ImageData | null;
|
|
88
|
+
static convertPixelToRec2100_hlg(pixel: Uint8ClampedArray): Float16Array;
|
|
89
|
+
static convertArrayToRec2100_hlg(data: Uint8ClampedArray): Float16Array;
|
|
90
|
+
pixelCallback(fn: HDRImagePixelCallback): void;
|
|
91
|
+
static fromImageData(imageData: HDRImageData): Float16Image;
|
|
92
|
+
static fromImageDataArray(width: number, height: number, imageDataArray: Uint8ClampedArray | Uint8ClampedArray<ArrayBufferLike>): Float16Image;
|
|
93
|
+
static fromURL(url: URL): Promise<Float16Image | undefined>;
|
|
94
|
+
setImageData(imageData: HDRImageData): void;
|
|
95
|
+
clone(): this;
|
|
48
96
|
}
|
|
49
97
|
|
|
50
98
|
declare function checkHDR(): boolean;
|
|
51
99
|
declare function checkHDRCanvas(): boolean;
|
|
52
100
|
|
|
53
|
-
declare function initHDRCanvas(canvas: HDRHTMLCanvasElement):
|
|
101
|
+
declare function initHDRCanvas(canvas: HDRHTMLCanvasElement): CanvasRenderingContext2DHDR | null;
|
|
54
102
|
declare function defaultGetContextHDR(): void;
|
|
55
103
|
declare function resetGetContext(): void;
|
|
56
104
|
|
|
57
|
-
export { Uint16Image, checkHDR, checkHDRCanvas, defaultGetContextHDR, initHDRCanvas, resetGetContext };
|
|
105
|
+
export { Float16Image, HDRImage, Uint16Image, checkHDR, checkHDRCanvas, defaultGetContextHDR, initHDRCanvas, resetGetContext };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# `hdr-canvas` 0.1.0
|
|
2
|
+
|
|
3
|
+
# Introduction
|
|
4
|
+
|
|
5
|
+
Since the last release many areas of handling HDR content in the browser have evolved.
|
|
6
|
+
Most notably is certainly the introduction of the `Float16Array` in the `ImageData` constructor:
|
|
7
|
+
|
|
8
|
+
- The [WhatWG spec](https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#imagedataarray), [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData#syntax) and [BCD](https://github.com/mdn/browser-compat-data/issues/27547)) have been updated accordingly. You can test your own browser using `new ImageData(new Float16Array(4), 1, 1, {pixelFormat:"rgba-float16"})`.
|
|
9
|
+
- Still open in [Firefox](https://bugzil.la/1958830)
|
|
10
|
+
- Hidden behind flag in [Safari](https://webkit.org/b/291196)
|
|
11
|
+
- Chromium has implemented it starting with [137](https://source.chromium.org/chromium/chromium/src/+/refs/tags/137.0.7104.0:third_party/blink/renderer/core/html/canvas/image_data.idl): \*\*The `ImageData` constructor only accepts `Float16Array` instead of `Uint16Array`. This makes older versions of this module obsolete, since they targeted the chromium specific solution.
|
|
12
|
+
- If Safari enables it by default it will be also in the [Typescript DOM types](https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/2107)
|
|
13
|
+
|
|
14
|
+
As [@reitowo](https://github.com/reitowo) pointed out, there has been a change to the `getContext("2d")` method. The key `pixelFormat` has been replaced by `colorType`.
|
|
15
|
+
|
|
16
|
+
In parallel there have been changes to the UltraHDR image format, especially the encoding of gain map metadata. While this used to be don in XMP it's now done according to ISO 21496-1. This has been adopted by Google and Apple in newer OS versions like Android 15 and iOS 18 to avoid cross-platform fragmentation. The [UltraHDR Library](https://github.com/google/libultrahdr) has already changed to using the [ISO format as default](https://github.com/google/libultrahdr/blob/main/docs/building.md).
|
|
17
|
+
|
|
18
|
+
Currently the ThreeJS UHDR loader doesn't know how to handle this change, see [mrdoob/three.js#32293](https://github.com/mrdoob/three.js/issues/32293).
|
|
19
|
+
|
|
20
|
+
# Key Changes & New Features
|
|
21
|
+
|
|
22
|
+
- Better support for official Web-APIs
|
|
23
|
+
- Use `Float16Array` instead of `Uint16Array`
|
|
24
|
+
- Use the correct option for initializing 2D canvas context
|
|
25
|
+
|
|
26
|
+
## Improved Documentation
|
|
27
|
+
|
|
28
|
+
The documentation have been greatly improved, there is now also a [site](https://cmahnke.github.io/hdr-canvas/) including the examples and API docs.
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
The examples from the blog are now part of this repository:
|
|
33
|
+
|
|
34
|
+
- [`tests/site/assets/ts/hdr-three.js.ts`](tests/site/assets/ts/hdr-three.js.ts) - Three JS with HDR texture
|
|
35
|
+
- [`tests/site/assets/ts/image-slider.ts`](tests/site/assets/ts/image-slider.ts) - Generated HDR content
|
|
36
|
+
- [`tests/site/assets/ts/main.ts`](tests/site/assets/ts/main.ts) - feature detection
|
|
37
|
+
|
|
38
|
+
These example are also avalable on the new [documentation site](https://cmahnke.github.io/hdr-canvas/)
|
|
39
|
+
|
|
40
|
+
## Advocacy
|
|
41
|
+
|
|
42
|
+
Since the changes by the WhatWG weren't picked up already there had to be some Issues in the relevant repos to be raised.
|
|
43
|
+
|
|
44
|
+
- [mdn/content#40639](https://github.com/mdn/content/issues/40639)
|
|
45
|
+
- [mdn/browser-compat-data#27547](https://github.com/mdn/browser-compat-data/issues/27547)
|
|
46
|
+
- [microsoft/TypeScript-DOM-lib-generator#2107](https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/2107)
|
|
47
|
+
- [mrdoob/three.js#32293](https://github.com/mrdoob/three.js/issues/32293).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# `hdr-canvas` 0.1.1
|
|
2
|
+
|
|
3
|
+
# Introduction
|
|
4
|
+
|
|
5
|
+
Since the last release many areas of handling HDR content in the browser have evolved. This also applies for WebGPU renderings using ThreeJS
|
|
6
|
+
|
|
7
|
+
The changes in initializing a `canvas for a Rendere are described in the [explainer](https://github.com/ccameron-chromium/webgpu-hdr/blob/main/EXPLAINER.md).
|
|
8
|
+
|
|
9
|
+
The important change is the renaming from `colorMetadata` to:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
toneMapping: { mode: "extended" }
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This is also reflected by the ThreeJS API so a custom HDR WebGPU Renderer and Backend aren't strictly needed anymore. They will be removed soon.
|
|
16
|
+
|
|
17
|
+
# Key Changes & New Features
|
|
18
|
+
|
|
19
|
+
- Fix ThreeJS HDR Renderer and Backend
|
package/docs/release.md
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hdr-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "HDR capable HTML canvas",
|
|
5
5
|
"main": "dist/hdr-canvas.cjs",
|
|
6
6
|
"module": "dist/hdr-canvas.js",
|
|
@@ -25,19 +25,33 @@
|
|
|
25
25
|
"dist/@types",
|
|
26
26
|
"three",
|
|
27
27
|
"src",
|
|
28
|
+
"docs",
|
|
29
|
+
"scripts",
|
|
30
|
+
"patches",
|
|
28
31
|
"rollup.config.mjs",
|
|
29
32
|
"tsconfig.json",
|
|
30
33
|
"README",
|
|
31
34
|
"LICENCE"
|
|
32
35
|
],
|
|
33
36
|
"types": "dist/index.d.ts",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24",
|
|
39
|
+
"deno": ">=1.43",
|
|
40
|
+
"bun": ">=1.1.23"
|
|
41
|
+
},
|
|
34
42
|
"scripts": {
|
|
35
|
-
"
|
|
36
|
-
"
|
|
43
|
+
"lint": "npm run eslint && npm run stylelint",
|
|
44
|
+
"eslint": "eslint . -c eslint.config.mjs --report-unused-disable-directives",
|
|
45
|
+
"stylelint": "stylelint -c tests/site/stylelint.config.js 'tests/**/*.scss'",
|
|
46
|
+
"knip": "knip",
|
|
37
47
|
"tsc": "tsc",
|
|
38
48
|
"build": "rimraf ./dist ./build && tsc && rollup --config",
|
|
49
|
+
"dev": "npm run jsdoc && vite -c tests/site/vite.config.js",
|
|
50
|
+
"site": "npm run jsdoc && vite -c tests/site/vite.config.js build",
|
|
39
51
|
"clean": "rimraf ./dist ./build",
|
|
40
|
-
"format": "prettier . --check"
|
|
52
|
+
"format": "prettier . --check",
|
|
53
|
+
"jsdoc": "jsdoc -c jsdoc.config.json -r .",
|
|
54
|
+
"test": "vitest --config tests/site/vite.config.js"
|
|
41
55
|
},
|
|
42
56
|
"repository": {
|
|
43
57
|
"type": "git",
|
|
@@ -56,20 +70,41 @@
|
|
|
56
70
|
},
|
|
57
71
|
"homepage": "https://github.com/cmahnke/hdr-canvas#readme",
|
|
58
72
|
"devDependencies": {
|
|
59
|
-
"@eslint/js": "^9.
|
|
60
|
-
"@
|
|
73
|
+
"@eslint/js": "^9.39.1",
|
|
74
|
+
"@eslint/markdown": "^7.5.1",
|
|
75
|
+
"@fontsource-utils/scss": "^0.2.2",
|
|
76
|
+
"@fontsource-variable/league-spartan": "^5.2.8",
|
|
77
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
61
78
|
"@rollup/plugin-terser": "^0.4.4",
|
|
62
|
-
"@rollup/plugin-typescript": "^12.
|
|
63
|
-
"@
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
79
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
80
|
+
"@napi-rs/canvas": "^0.1.83",
|
|
81
|
+
"@types/jsdom": "^27.0.0",
|
|
82
|
+
"@types/semver": "^7.7.1",
|
|
83
|
+
"@types/three": "^0.181.0",
|
|
84
|
+
"@typescript-eslint/parser": "^8.48.0",
|
|
85
|
+
"@typescript/lib-dom": "npm:@types/web@^0.0.294",
|
|
86
|
+
"better-docs": "^2.7.3",
|
|
87
|
+
"eslint": "^9.39.1",
|
|
88
|
+
"file-url": "^4.0.0",
|
|
89
|
+
"globals": "^16.5.0",
|
|
90
|
+
"jsdoc": "^4.0.5",
|
|
91
|
+
"jsdom": "^27.2.0",
|
|
92
|
+
"knip": "^5.70.2",
|
|
93
|
+
"pkg-types": "^2.3.0",
|
|
94
|
+
"prettier": "^3.7.1",
|
|
95
|
+
"rimraf": "^6.1.2",
|
|
96
|
+
"rollup": "^4.53.3",
|
|
97
|
+
"rollup-plugin-dts": "^6.3.0",
|
|
98
|
+
"sass": "^1.94.2",
|
|
99
|
+
"semver": "^7.7.3",
|
|
100
|
+
"stylelint": "^16.26.1",
|
|
101
|
+
"stylelint-config-standard-scss": "^16.0.0",
|
|
102
|
+
"three": "^0.181.2",
|
|
70
103
|
"tslib": "^2.8.1",
|
|
71
|
-
"typescript": "^5.
|
|
72
|
-
"typescript-eslint": "^8.
|
|
104
|
+
"typescript": "^5.9.3",
|
|
105
|
+
"typescript-eslint": "^8.48.0",
|
|
106
|
+
"vite": "^7.2.4",
|
|
107
|
+
"vitest": "^4.0.14"
|
|
73
108
|
},
|
|
74
109
|
"dependencies": {
|
|
75
110
|
"colorjs.io": "^0.5.2"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
diff --git a/node_modules/@typescript/dom-lib-generator/inputfiles/overridingTypes.jsonc b/node_modules/@typescript/dom-lib-generator/inputfiles/overridingTypes.jsonc
|
|
2
|
+
index 7c11453..c7f0bbb 100644
|
|
3
|
+
--- a/node_modules/@typescript/dom-lib-generator/inputfiles/overridingTypes.jsonc
|
|
4
|
+
+++ b/node_modules/@typescript/dom-lib-generator/inputfiles/overridingTypes.jsonc
|
|
5
|
+
@@ -4035,6 +4035,23 @@
|
|
6
|
+
}
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
+ {
|
|
10
|
+
+ "name": "ImageDataArray",
|
|
11
|
+
+ "typeParameters": [
|
|
12
|
+
+ {
|
|
13
|
+
+ "name": "ArrayBuffer"
|
|
14
|
+
+ }
|
|
15
|
+
+ ],
|
|
16
|
+
+ "type": [
|
|
17
|
+
+ {
|
|
18
|
+
+ "type": "Uint8ClampedArray"
|
|
19
|
+
+ },
|
|
20
|
+
+ {
|
|
21
|
+
+ "type": "Float16Array"
|
|
22
|
+
+ }
|
|
23
|
+
+ ],
|
|
24
|
+
+ "overrideType": "Uint8ClampedArray<ArrayBuffer> | Float16Array<ArrayBuffer>"
|
|
25
|
+
+ },
|
|
26
|
+
{
|
|
27
|
+
"name": "ReadableStreamReader",
|
|
28
|
+
"typeParameters": [
|
|
29
|
+
diff --git a/node_modules/@typescript/dom-lib-generator/inputfiles/removedTypes.jsonc b/node_modules/@typescript/dom-lib-generator/inputfiles/removedTypes.jsonc
|
|
30
|
+
index 8089030..d93ee40 100644
|
|
31
|
+
--- a/node_modules/@typescript/dom-lib-generator/inputfiles/removedTypes.jsonc
|
|
32
|
+
+++ b/node_modules/@typescript/dom-lib-generator/inputfiles/removedTypes.jsonc
|
|
33
|
+
@@ -410,13 +410,6 @@
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
- "ImageDataSettings": {
|
|
38
|
+
- "members": {
|
|
39
|
+
- "member": {
|
|
40
|
+
- "pixelFormat": null // Blink experimental only as of 2025-04
|
|
41
|
+
- }
|
|
42
|
+
- }
|
|
43
|
+
- },
|
|
44
|
+
"IntersectionObserverInit": {
|
|
45
|
+
"members": {
|
|
46
|
+
"member": {
|
package/scripts/check.sh
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, rmSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import git from "isomorphic-git";
|
|
4
|
+
import http from "isomorphic-git/http/node";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import yargs from "yargs";
|
|
7
|
+
import { hideBin } from "yargs/helpers";
|
|
8
|
+
|
|
9
|
+
const argv = yargs(hideBin(process.argv))
|
|
10
|
+
.option("directory", {
|
|
11
|
+
alias: "d",
|
|
12
|
+
type: "string",
|
|
13
|
+
default: process.cwd(),
|
|
14
|
+
description: "The root directory containing the .gitmodules file."
|
|
15
|
+
})
|
|
16
|
+
.option("force", {
|
|
17
|
+
alias: "f",
|
|
18
|
+
type: "boolean",
|
|
19
|
+
default: false,
|
|
20
|
+
description: "Force a clean checkout by removing existing directories."
|
|
21
|
+
})
|
|
22
|
+
.option("verbose", {
|
|
23
|
+
alias: "v",
|
|
24
|
+
type: "boolean",
|
|
25
|
+
default: false,
|
|
26
|
+
description: "Show verbose output during the cloning process."
|
|
27
|
+
})
|
|
28
|
+
.option("list", {
|
|
29
|
+
alias: "l",
|
|
30
|
+
type: "boolean",
|
|
31
|
+
default: false,
|
|
32
|
+
description: "List submodules and their directories in a table."
|
|
33
|
+
})
|
|
34
|
+
.option("clean", {
|
|
35
|
+
alias: "c",
|
|
36
|
+
type: "boolean",
|
|
37
|
+
default: false,
|
|
38
|
+
description: "Remove all directories listed in the .gitmodules file."
|
|
39
|
+
})
|
|
40
|
+
.option("timeout", {
|
|
41
|
+
alias: "t",
|
|
42
|
+
type: "number",
|
|
43
|
+
default: 120000, // Default timeout of 120 seconds (120000ms)
|
|
44
|
+
description: "Set the timeout for git operations in milliseconds."
|
|
45
|
+
})
|
|
46
|
+
.check((argv) => {
|
|
47
|
+
const actionFlags = [argv.list, argv.clean];
|
|
48
|
+
const trueFlags = actionFlags.filter((flag) => flag === true);
|
|
49
|
+
if (trueFlags.length > 1) {
|
|
50
|
+
throw new Error("Please use only one of the action flags: --list or --clean.");
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
})
|
|
54
|
+
.help()
|
|
55
|
+
.alias("help", "h").argv;
|
|
56
|
+
|
|
57
|
+
function getSubmoduleEntries(dir) {
|
|
58
|
+
const gitmodulesPath = resolve(dir, ".gitmodules");
|
|
59
|
+
if (!existsSync(gitmodulesPath)) {
|
|
60
|
+
console.error(`Error: No .gitmodules file found in the specified directory: ${dir}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const gitmodulesContent = readFileSync(gitmodulesPath, "utf8");
|
|
65
|
+
const submoduleEntries = gitmodulesContent.split("\n\n").filter((entry) => entry.trim().startsWith("[submodule"));
|
|
66
|
+
if (submoduleEntries.length === 0) {
|
|
67
|
+
console.log("No submodules found in .gitmodules file. Exiting.");
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const submodules = [];
|
|
72
|
+
for (const entry of submoduleEntries) {
|
|
73
|
+
const lines = entry.split("\n").map((line) => line.trim());
|
|
74
|
+
let pathValue, urlValue;
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
if (line.startsWith("path =")) {
|
|
77
|
+
pathValue = line.split("=")[1].trim();
|
|
78
|
+
} else if (line.startsWith("url =")) {
|
|
79
|
+
urlValue = line.split("=")[1].trim();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (pathValue && urlValue) {
|
|
83
|
+
submodules.push({ path: pathValue, url: urlValue });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return submodules;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function printTable(data) {
|
|
90
|
+
const table = [
|
|
91
|
+
["URL", "Path"],
|
|
92
|
+
["---", "---"]
|
|
93
|
+
];
|
|
94
|
+
data.forEach((sub) => {
|
|
95
|
+
table.push([`\`${sub.url}\``, `\`${sub.path}\``]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const maxCols = [0, 0];
|
|
99
|
+
table.forEach((row) => {
|
|
100
|
+
maxCols[0] = Math.max(maxCols[0], row[0].length);
|
|
101
|
+
maxCols[1] = Math.max(maxCols[1], row[1].length);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
table.forEach((row) => {
|
|
105
|
+
const formattedRow = `| ${row[0].padEnd(maxCols[0])} | ${row[1].padEnd(maxCols[1])} |`;
|
|
106
|
+
console.log(formattedRow);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function main() {
|
|
111
|
+
const dir = resolve(argv.directory);
|
|
112
|
+
const submodules = getSubmoduleEntries(dir);
|
|
113
|
+
|
|
114
|
+
if (argv.list) {
|
|
115
|
+
console.log("Submodules in .gitmodules:");
|
|
116
|
+
printTable(submodules);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (argv.clean) {
|
|
121
|
+
console.log("Removing submodule directories...");
|
|
122
|
+
for (const sub of submodules) {
|
|
123
|
+
const submoduleDir = resolve(dir, sub.path);
|
|
124
|
+
if (existsSync(submoduleDir)) {
|
|
125
|
+
console.log(`Removing directory: ${submoduleDir}`);
|
|
126
|
+
rmSync(submoduleDir, { recursive: true, force: true });
|
|
127
|
+
} else {
|
|
128
|
+
console.log(`Directory not found, skipping: ${submoduleDir}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
console.log("Clean operation complete.");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const sub of submodules) {
|
|
136
|
+
const submoduleDir = resolve(dir, sub.path);
|
|
137
|
+
|
|
138
|
+
if (argv.verbose) {
|
|
139
|
+
console.log(`Processing submodule:`);
|
|
140
|
+
console.log(` URL: ${sub.url}`);
|
|
141
|
+
console.log(` Path: ${submoduleDir}`);
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`Cloning submodule from ${sub.url} into ${submoduleDir}...`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
if (argv.force && existsSync(submoduleDir)) {
|
|
148
|
+
if (argv.verbose) {
|
|
149
|
+
console.log(`Force flag enabled. Removing existing directory: ${submoduleDir}`);
|
|
150
|
+
}
|
|
151
|
+
rmSync(submoduleDir, { recursive: true, force: true });
|
|
152
|
+
} else if (existsSync(submoduleDir)) {
|
|
153
|
+
if (argv.verbose) {
|
|
154
|
+
console.log(`Directory ${submoduleDir} already exists. Skipping clone.`);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await git.clone({
|
|
160
|
+
fs: fs,
|
|
161
|
+
http: { ...http, timeout: argv.timeout },
|
|
162
|
+
dir: submoduleDir,
|
|
163
|
+
url: sub.url,
|
|
164
|
+
singleBranch: true,
|
|
165
|
+
depth: 1
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
console.log(`Successfully cloned submodule into ${submoduleDir}.`);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(`Error cloning submodule into ${submoduleDir}:`, error.message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
main();
|