@kurone-kito/sea-builder 0.20.0-alpha.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kuroné Kito (黒音キト)
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.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # 🟦 `@kurone-kito/sea-builder`
2
+
3
+ SEA (Single Executable Application) Builder for the Node.js apps
4
+
5
+ ## System Requirements
6
+
7
+ - Node.js: Any of the following versions
8
+ - Iron LTS (`^20.11.x`)
9
+ - Jod LTS (`^22.x.x`)
10
+ - Latest (`>=24.x.x`)
11
+
12
+ ## Usage
13
+
14
+ The package provides two commands: `sea-builder` for building the binary
15
+ and `sea-cache` for fetching Node.js archives used by the builder.
16
+
17
+ ### 1. **Cache Node.js archives**
18
+
19
+ ```sh
20
+ sea-cache # cache for current platform
21
+ sea-cache linux-x64 win32-x64 # cache archives for multiple targets
22
+ ```
23
+
24
+ Archives are downloaded to `node_modules/.cache/xsea` if not already
25
+ present.
26
+
27
+ ### 2. **Build the SEA binary**
28
+
29
+ ```sh
30
+ sea-builder # build for current platform
31
+ sea-builder linux-x64 win32-x64 # build for multiple targets
32
+ ```
33
+
34
+ When no targets are specified, the current platform and architecture are
35
+ used.
36
+
37
+ ### Options
38
+
39
+ Both commands accept target strings in the format `<platform>-<arch>` such
40
+ as `linux-x64` or `win32-x64`. `sea-builder` automatically invokes
41
+ `pnpm exec xsea` with the downloaded archives to create the binary under
42
+ `sea/pwt-cli`.
43
+
44
+ ### Cache directory
45
+
46
+ All downloaded archives are stored in `node_modules/.cache/xsea`. You can
47
+ delete this folder to clear the cache at any time.
48
+
49
+ ## LICENSE
50
+
51
+ MIT
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node --enable-source-maps
2
+ import { realpathSync, existsSync, mkdirSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { isNativeError } from "node:util/types";
5
+ import { execa } from "execa";
6
+ import { Listr } from "listr2";
7
+ import { n as normalizeTargets, createCacheTasks } from "./cache.mjs";
8
+ import { parseArgs } from "node:util";
9
+ import "node:http";
10
+ import "node:https";
11
+ import "node:path";
12
+ import "node:stream/promises";
13
+ const staticConfig = {
14
+ allowPositionals: true,
15
+ options: {
16
+ help: { default: false, short: "h", type: "boolean" },
17
+ targets: { type: "string" }
18
+ }
19
+ };
20
+ const parseCliArgs = (argv) => {
21
+ const {
22
+ values,
23
+ positionals: [base]
24
+ } = parseArgs({ ...staticConfig, args: [...argv] });
25
+ if (values.help) {
26
+ return { help: true };
27
+ }
28
+ if (!base) {
29
+ throw new Error("Output file base name is required");
30
+ }
31
+ const list = values.targets?.split(",").filter(Boolean) ?? [];
32
+ return { help: false, basename: base, targets: list };
33
+ };
34
+ const usage = () => console.log(
35
+ "Usage: sea-builder --targets=<target>[,<target>[,...]] <output>"
36
+ );
37
+ const buildTask = (runExeca) => ({
38
+ title: "Build",
39
+ task: () => runExeca("pnpm", ["run", "build"], { stdio: "inherit" })
40
+ });
41
+ const cacheTask = (opts) => ({
42
+ title: "Download the Node.js archives",
43
+ task: () => createCacheTasks(opts).run()
44
+ });
45
+ const seaTask = (runExeca, targets, basename) => ({
46
+ title: "Linking the SEA binary",
47
+ task: () => {
48
+ const args = [
49
+ "exec",
50
+ "xsea",
51
+ "dist/index.mjs",
52
+ "-o",
53
+ `sea/${basename}`,
54
+ ...targets.flatMap((t) => ["-t", t])
55
+ ];
56
+ return runExeca("pnpm", args, { stdio: "inherit" });
57
+ }
58
+ });
59
+ const run = (options) => {
60
+ const {
61
+ arch = process.arch,
62
+ basename,
63
+ download: runDownload,
64
+ execa: runExeca = execa,
65
+ existsSync: runExistsSync = existsSync,
66
+ mkdirSync: runMkdirSync = mkdirSync,
67
+ nodeVersion = `v${process.versions.node}`,
68
+ platform = process.platform,
69
+ projectRoot,
70
+ targets = []
71
+ } = options;
72
+ const list = normalizeTargets(targets, platform, arch);
73
+ return new Listr([
74
+ buildTask(runExeca),
75
+ cacheTask({
76
+ targets: list,
77
+ download: runDownload,
78
+ existsSync: runExistsSync,
79
+ mkdirSync: runMkdirSync,
80
+ projectRoot,
81
+ nodeVersion
82
+ }),
83
+ seaTask(runExeca, list, basename)
84
+ ]).run();
85
+ };
86
+ const [__, bin = "", ...rawArgs] = process.argv;
87
+ if (realpathSync(bin) === fileURLToPath(import.meta.url)) {
88
+ try {
89
+ const args = parseCliArgs(rawArgs);
90
+ if (args.help) {
91
+ usage();
92
+ } else {
93
+ await run(args);
94
+ }
95
+ } catch (err) {
96
+ if (isNativeError(err)) {
97
+ console.error(err.message);
98
+ }
99
+ process.exitCode = 1;
100
+ }
101
+ }
102
+ export {
103
+ run
104
+ };
105
+ //# sourceMappingURL=builder.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.mjs","sources":["../src/parseArgs.mts","../src/usage.mts","../src/builder.mts"],"sourcesContent":["import type { ParseArgsConfig } from 'node:util';\nimport { parseArgs } from 'node:util';\n\n/** Type definition for parsed CLI arguments. */\nexport interface HelpParsedArgs {\n /** Indicates that help was requested. */\n readonly help: true;\n}\n\n/** Type definition for parsed CLI arguments. */\nexport interface TargetsParsedArgs {\n /** Indicates that help was not requested. */\n readonly help: false;\n\n /** The base name for the output file. */\n readonly basename: string;\n\n /** A list of target platforms. */\n readonly targets: readonly string[];\n}\n\n/** Static configuration for CLI argument parsing. */\nconst staticConfig = {\n allowPositionals: true,\n options: {\n help: { default: false, short: 'h', type: 'boolean' },\n targets: { type: 'string' },\n },\n} as const satisfies ParseArgsConfig;\n\n/**\n * Parse CLI arguments into options for {@link run}.\n * @param argv CLI arguments.\n * @returns Parsed values.\n */\nexport const parseCliArgs = (\n argv: readonly string[],\n): HelpParsedArgs | TargetsParsedArgs => {\n const {\n values,\n positionals: [base],\n } = parseArgs({ ...staticConfig, args: [...argv] });\n if (values.help) {\n return { help: true };\n }\n if (!base) {\n throw new Error('Output file base name is required');\n }\n const list = values.targets?.split(',').filter(Boolean) ?? [];\n return { help: false, basename: base, targets: list };\n};\n","/** Display help message. */\nexport const usage = (): void =>\n console.log(\n 'Usage: sea-builder --targets=<target>[,<target>[,...]] <output>',\n );\n","#!/usr/bin/env node --enable-source-maps\n\nimport { existsSync, mkdirSync, realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { isNativeError } from 'node:util/types';\nimport { execa } from 'execa';\nimport { Listr } from 'listr2';\nimport type { CacheOptions } from './cache.mjs';\nimport { createCacheTasks } from './cache.mjs';\nimport { parseCliArgs } from './parseArgs.mts';\nimport { normalizeTargets } from './targets.mjs';\nimport { usage } from './usage.mjs';\n\n/** Options accepted by {@link run}. */\nexport interface RunOptions extends CacheOptions {\n /** `execa` implementation. */\n readonly execa?: typeof execa | undefined;\n\n /** Output file base name. */\n readonly basename: string;\n}\n\n/**\n * Create a task for building the project.\n * @param runExeca The implementation of {@link execa} to use.\n * @returns Listr task object.\n */\nconst buildTask = (runExeca: typeof execa) => ({\n title: 'Build',\n task: () => runExeca('pnpm', ['run', 'build'], { stdio: 'inherit' }),\n});\n\n/**\n * Create a task for caching Node.js archives.\n * @param opts Options for cache creation including targets.\n * @returns Listr task object.\n */\nconst cacheTask = (\n opts: CacheOptions & { readonly targets: readonly string[] },\n) => ({\n title: 'Download the Node.js archives',\n task: () => createCacheTasks(opts).run(),\n});\n\n/**\n * Create a task for generating the SEA binary.\n * @param runExeca The implementation of {@link execa} to use.\n * @param targets Normalized target strings.\n * @param basename Base name for the output file.\n * @returns Listr task object.\n */\nconst seaTask = (\n runExeca: typeof execa,\n targets: readonly string[],\n basename: string,\n) => ({\n title: 'Linking the SEA binary',\n task: () => {\n const args = [\n 'exec',\n 'xsea',\n 'dist/index.mjs',\n '-o',\n `sea/${basename}`,\n ...targets.flatMap((t) => ['-t', t]),\n ];\n return runExeca('pnpm', args, { stdio: 'inherit' });\n },\n});\n\n/**\n * Execute SEA build process.\n *\n * This function runs a series of tasks to build the project, download\n * required Node.js archives and finally generate the SEA binary.\n * @param options Options controlling the build behaviour.\n */\nexport const run = (options: RunOptions): Promise<void> => {\n const {\n arch = process.arch,\n basename,\n download: runDownload,\n execa: runExeca = execa,\n existsSync: runExistsSync = existsSync,\n mkdirSync: runMkdirSync = mkdirSync,\n nodeVersion = `v${process.versions.node}`,\n platform = process.platform,\n projectRoot,\n targets = [],\n } = options;\n const list = normalizeTargets(targets, platform, arch);\n return new Listr([\n buildTask(runExeca),\n cacheTask({\n targets: list,\n download: runDownload,\n existsSync: runExistsSync,\n mkdirSync: runMkdirSync,\n projectRoot,\n nodeVersion,\n }),\n seaTask(runExeca, list, basename),\n ]).run();\n};\n\nconst [__, bin = '', ...rawArgs] = process.argv;\nif (realpathSync(bin) === fileURLToPath(import.meta.url)) {\n try {\n const args = parseCliArgs(rawArgs);\n if (args.help) {\n usage();\n } else {\n await run(args);\n }\n } catch (err) {\n if (isNativeError(err)) {\n console.error(err.message);\n }\n process.exitCode = 1;\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAsBA,MAAM,eAAe;AAAA,EACnB,kBAAkB;AAAA,EAClB,SAAS;AAAA,IACP,MAAM,EAAE,SAAS,OAAO,OAAO,KAAK,MAAM,UAAA;AAAA,IAC1C,SAAS,EAAE,MAAM,SAAA;AAAA,EAAS;AAE9B;AAOO,MAAM,eAAe,CAC1B,SACuC;AACvC,QAAM;AAAA,IACJ;AAAA,IACA,aAAa,CAAC,IAAI;AAAA,EAAA,IAChB,UAAU,EAAE,GAAG,cAAc,MAAM,CAAC,GAAG,IAAI,GAAG;AAClD,MAAI,OAAO,MAAM;AACf,WAAO,EAAE,MAAM,KAAA;AAAA,EAAK;AAEtB,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,mCAAmC;AAAA,EAAA;AAErD,QAAM,OAAO,OAAO,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,KAAK,CAAA;AAC3D,SAAO,EAAE,MAAM,OAAO,UAAU,MAAM,SAAS,KAAA;AACjD;ACjDO,MAAM,QAAQ,MACnB,QAAQ;AAAA,EACN;AACF;ACuBF,MAAM,YAAY,CAAC,cAA4B;AAAA,EAC7C,OAAO;AAAA,EACP,MAAM,MAAM,SAAS,QAAQ,CAAC,OAAO,OAAO,GAAG,EAAE,OAAO,UAAA,CAAW;AACrE;AAOA,MAAM,YAAY,CAChB,UACI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,MAAM,iBAAiB,IAAI,EAAE,IAAA;AACrC;AASA,MAAM,UAAU,CACd,UACA,SACA,cACI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,MAAM;AACV,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,GAAG,QAAQ,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAAA,IAAA;AAErC,WAAO,SAAS,QAAQ,MAAM,EAAE,OAAO,WAAW;AAAA,EAAA;AAEtD;AASO,MAAM,MAAM,CAAC,YAAuC;AACzD,QAAM;AAAA,IACJ,OAAO,QAAQ;AAAA,IACf;AAAA,IACA,UAAU;AAAA,IACV,OAAO,WAAW;AAAA,IAClB,YAAY,gBAAgB;AAAA,IAC5B,WAAW,eAAe;AAAA,IAC1B,cAAc,IAAI,QAAQ,SAAS,IAAI;AAAA,IACvC,WAAW,QAAQ;AAAA,IACnB;AAAA,IACA,UAAU,CAAA;AAAA,EAAC,IACT;AACJ,QAAM,OAAO,iBAAiB,SAAS,UAAU,IAAI;AACrD,SAAO,IAAI,MAAM;AAAA,IACf,UAAU,QAAQ;AAAA,IAClB,UAAU;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IAAA,CACD;AAAA,IACD,QAAQ,UAAU,MAAM,QAAQ;AAAA,EAAA,CACjC,EAAE,IAAA;AACL;AAEA,MAAM,CAAC,IAAI,MAAM,IAAI,GAAG,OAAO,IAAI,QAAQ;AAC3C,IAAI,aAAa,GAAG,MAAM,cAAc,YAAY,GAAG,GAAG;AACxD,MAAI;AACF,UAAM,OAAO,aAAa,OAAO;AACjC,QAAI,KAAK,MAAM;AACb,YAAA;AAAA,IAAM,OACD;AACL,YAAM,IAAI,IAAI;AAAA,IAAA;AAAA,EAChB,SACO,KAAK;AACZ,QAAI,cAAc,GAAG,GAAG;AACtB,cAAQ,MAAM,IAAI,OAAO;AAAA,IAAA;AAE3B,YAAQ,WAAW;AAAA,EAAA;AAEvB;"}
package/dist/cache.mjs ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node --enable-source-maps
2
+ import { realpathSync, existsSync, mkdirSync, createWriteStream } from "node:fs";
3
+ import http from "node:http";
4
+ import https from "node:https";
5
+ import { join, dirname } from "node:path";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import { Listr } from "listr2";
9
+ const normalizeTargets = (targets, platform, arch) => targets.length ? [...targets] : [`${platform}-${arch}`];
10
+ const findProjectRoot = (startPath, runExistsSync) => {
11
+ let currentPath = startPath;
12
+ let parentPath = dirname(currentPath);
13
+ while (currentPath !== parentPath) {
14
+ if (runExistsSync(join(currentPath, "package.json")) || runExistsSync(join(currentPath, "node_modules"))) {
15
+ return currentPath;
16
+ }
17
+ currentPath = parentPath;
18
+ parentPath = dirname(currentPath);
19
+ }
20
+ return startPath;
21
+ };
22
+ const createMetaFactory = (cacheDir, nodeVersion) => (target) => {
23
+ const ext = target.startsWith("win") ? "zip" : "tar.gz";
24
+ const archive = `node-${nodeVersion}-${target}.${ext}`;
25
+ return {
26
+ target,
27
+ archivePath: join(cacheDir, archive),
28
+ url: `https://nodejs.org/dist/${nodeVersion}/${archive}`
29
+ };
30
+ };
31
+ const downloadFile = async (url, dest) => {
32
+ const { protocol } = new URL(url);
33
+ const getter = protocol === "http:" ? http.get : https.get;
34
+ await new Promise((resolve, reject) => {
35
+ getter(url, (res) => {
36
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
37
+ downloadFile(res.headers.location, dest).then(resolve, reject);
38
+ return;
39
+ }
40
+ if (res.statusCode !== 200) {
41
+ reject(new Error(`Failed to download: ${res.statusCode}`));
42
+ return;
43
+ }
44
+ const file = createWriteStream(dest);
45
+ pipeline(res, file).then(resolve).catch(reject);
46
+ }).on("error", reject);
47
+ });
48
+ };
49
+ const createTaskFactory = (runDownload, runExistsSync, runMkdirSync, cacheDir) => ({ target, archivePath, url }) => ({
50
+ title: target,
51
+ task: async () => {
52
+ runMkdirSync(cacheDir, { recursive: true });
53
+ if (!runExistsSync(archivePath)) {
54
+ await runDownload(url, archivePath);
55
+ }
56
+ }
57
+ });
58
+ const createCacheTasks = (options = {}) => {
59
+ const {
60
+ arch = process.arch,
61
+ download: runDownload = downloadFile,
62
+ existsSync: runExistsSync = existsSync,
63
+ mkdirSync: runMkdirSync = mkdirSync,
64
+ nodeVersion = `v${process.versions.node}`,
65
+ platform = process.platform,
66
+ projectRoot,
67
+ targets = []
68
+ } = options;
69
+ const list = normalizeTargets(targets, platform, arch);
70
+ const detectedRoot = projectRoot || findProjectRoot(process.cwd(), runExistsSync);
71
+ const cacheDir = join(detectedRoot, "node_modules/.cache/xsea");
72
+ const metaFor = createMetaFactory(cacheDir, nodeVersion);
73
+ const toTask = createTaskFactory(
74
+ runDownload,
75
+ runExistsSync,
76
+ runMkdirSync,
77
+ cacheDir
78
+ );
79
+ return new Listr(
80
+ list.map((t) => toTask(metaFor(t))),
81
+ { concurrent: true }
82
+ );
83
+ };
84
+ const cache = async (options = {}) => {
85
+ await createCacheTasks(options).run();
86
+ };
87
+ if (realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
88
+ await cache({ targets: process.argv.slice(2) });
89
+ }
90
+ export {
91
+ cache,
92
+ createCacheTasks,
93
+ normalizeTargets as n
94
+ };
95
+ //# sourceMappingURL=cache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.mjs","sources":["../src/targets.mts","../src/cache.mts"],"sourcesContent":["/**\n * Normalize target list for SEA build.\n *\n * When {@link targets} is empty, this function falls back to the current\n * {@link platform} and {@link arch} to create a single target string.\n * @param targets Target strings such as `linux-x64`.\n * @param platform Host platform name.\n * @param arch Host architecture name.\n * @returns Normalized target array.\n */\nexport const normalizeTargets = (\n targets: readonly string[],\n platform: NodeJS.Platform,\n arch: string,\n): readonly string[] =>\n targets.length ? [...targets] : [`${platform}-${arch}`];\n","#!/usr/bin/env node --enable-source-maps\n\nimport {\n createWriteStream,\n existsSync,\n mkdirSync,\n realpathSync,\n} from 'node:fs';\nimport http from 'node:http';\nimport https from 'node:https';\nimport { dirname, join } from 'node:path';\nimport { pipeline } from 'node:stream/promises';\nimport { fileURLToPath } from 'node:url';\nimport { Listr } from 'listr2';\nimport { normalizeTargets } from './targets.mjs';\n\n/**\n * Find the project root by looking for package.json or node_modules.\n * @param startPath Starting directory path.\n * @param runExistsSync File existence check function.\n * @returns Project root directory path.\n */\nconst findProjectRoot = (\n startPath: string,\n runExistsSync: typeof existsSync,\n): string => {\n let currentPath = startPath;\n let parentPath = dirname(currentPath);\n\n while (currentPath !== parentPath) {\n if (\n runExistsSync(join(currentPath, 'package.json')) ||\n runExistsSync(join(currentPath, 'node_modules'))\n ) {\n return currentPath;\n }\n currentPath = parentPath;\n parentPath = dirname(currentPath);\n }\n\n // If no project root is found, fall back to the starting path\n return startPath;\n};\n\n/** Metadata for a Node.js archive. */\ninterface ArchiveMeta {\n /** Local archive path. */\n readonly archivePath: string;\n\n /** Target string such as `linux-x64`. */\n readonly target: string;\n\n /** Download URL of the archive. */\n readonly url: string;\n}\n\n/** Options accepted by {@link cache} and {@link createCacheTasks}. */\nexport interface CacheOptions {\n /** Target strings such as `linux-x64`. */\n readonly targets?: readonly string[] | undefined;\n\n /** File download function used to fetch archives. */\n readonly download?:\n | ((url: string, dest: string) => Promise<void>)\n | undefined;\n\n /** `fs.existsSync` implementation. */\n readonly existsSync?: typeof existsSync | undefined;\n\n /** `fs.mkdirSync` implementation. */\n readonly mkdirSync?: typeof mkdirSync | undefined;\n\n /** Project root directory path. If not provided, will be auto-detected. */\n readonly projectRoot?: string | undefined;\n\n /** Platform identifier. */\n readonly platform?: NodeJS.Platform | undefined;\n\n /** Architecture identifier. */\n readonly arch?: string | undefined;\n\n /** Node.js version string. */\n readonly nodeVersion?: string | undefined;\n}\n\n/**\n * Create a function that generates metadata for a given target.\n * @param cacheDir Directory to store archives in.\n * @param nodeVersion Node.js version string.\n * @returns Function producing {@link ArchiveMeta} objects.\n */\nconst createMetaFactory =\n (cacheDir: string, nodeVersion: string) =>\n (target: string): ArchiveMeta => {\n const ext = target.startsWith('win') ? 'zip' : 'tar.gz';\n const archive = `node-${nodeVersion}-${target}.${ext}`;\n return {\n target,\n archivePath: join(cacheDir, archive),\n url: `https://nodejs.org/dist/${nodeVersion}/${archive}`,\n };\n };\n\n/**\n * Download a file from the given URL.\n * @param url Source URL.\n * @param dest Destination file path.\n */\nconst downloadFile = async (url: string, dest: string): Promise<void> => {\n const { protocol } = new URL(url);\n const getter = protocol === 'http:' ? http.get : https.get;\n await new Promise<void>((resolve, reject) => {\n getter(url, (res) => {\n if (\n res.statusCode &&\n res.statusCode >= 300 &&\n res.statusCode < 400 &&\n res.headers.location\n ) {\n downloadFile(res.headers.location, dest).then(resolve, reject);\n return;\n }\n if (res.statusCode !== 200) {\n reject(new Error(`Failed to download: ${res.statusCode}`));\n return;\n }\n const file = createWriteStream(dest);\n pipeline(res, file).then(resolve).catch(reject);\n }).on('error', reject);\n });\n};\n\n/**\n * Create a function that converts metadata into a Listr task.\n * @param runDownload File download function.\n * @param runExistsSync File existence check function.\n * @param runMkdirSync Directory creation function.\n * @param cacheDir Directory for cached archives.\n * @returns Function producing Listr tasks.\n */\nconst createTaskFactory =\n (\n runDownload: (url: string, dest: string) => Promise<void>,\n runExistsSync: typeof existsSync,\n runMkdirSync: typeof mkdirSync,\n cacheDir: string,\n ) =>\n ({ target, archivePath, url }: ArchiveMeta) => ({\n title: target,\n task: async () => {\n runMkdirSync(cacheDir, { recursive: true });\n if (!runExistsSync(archivePath)) {\n await runDownload(url, archivePath);\n }\n },\n });\n\n/**\n * Create Listr tasks for downloading Node.js archives.\n * @param options Options controlling the task generation.\n * @returns Configured {@link Listr} instance.\n */\nexport const createCacheTasks = (options: CacheOptions = {}): Listr => {\n const {\n arch = process.arch,\n download: runDownload = downloadFile,\n existsSync: runExistsSync = existsSync,\n mkdirSync: runMkdirSync = mkdirSync,\n nodeVersion = `v${process.versions.node}`,\n platform = process.platform,\n projectRoot,\n targets = [],\n } = options;\n const list = normalizeTargets(targets, platform, arch);\n const detectedRoot =\n projectRoot || findProjectRoot(process.cwd(), runExistsSync);\n const cacheDir = join(detectedRoot, 'node_modules/.cache/xsea');\n const metaFor = createMetaFactory(cacheDir, nodeVersion);\n const toTask = createTaskFactory(\n runDownload,\n runExistsSync,\n runMkdirSync,\n cacheDir,\n );\n return new Listr(\n list.map((t) => toTask(metaFor(t))),\n { concurrent: true },\n );\n};\n\n/**\n * Download Node.js archives if they are not cached.\n * @param options Options controlling the download behaviour.\n */\nexport const cache = async (options: CacheOptions = {}): Promise<void> => {\n await createCacheTasks(options).run();\n};\n// @ts-expect-error\nif (realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {\n await cache({ targets: process.argv.slice(2) });\n}\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@kurone-kito/sea-builder",
3
+ "version": "0.20.0-alpha.3",
4
+ "description": "The Single Executable App (SEA) builder for the Node.js",
5
+ "keywords": [
6
+ "sea",
7
+ "builder",
8
+ "boilerplate",
9
+ "template"
10
+ ],
11
+ "homepage": "https://github.com/kurone-kito/builder-config#readme",
12
+ "bugs": "https://github.com/kurone-kito/builder-config/issues",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/kurone-kito/builder-config.git",
16
+ "directory": "packages/sea-builder"
17
+ },
18
+ "license": "MIT",
19
+ "author": "kurone-kito <krone@kit.black> (https://kit.black/)",
20
+ "type": "module",
21
+ "bin": {
22
+ "sea-builder": "./dist/builder.mjs",
23
+ "sea-cache": "./dist/cache.mjs"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "dependencies": {
29
+ "execa": "^9.6.0",
30
+ "listr2": "^8.3.3"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^24.0.4",
34
+ "@vitest/coverage-v8": "^3.2.4",
35
+ "cpy-cli": "^5.0.0",
36
+ "rimraf": "^6.0.1",
37
+ "typescript": "~5.8.3",
38
+ "vite": "^7.0.0",
39
+ "vitest": "^3.2.4",
40
+ "@kurone-kito/typescript-config": "^0.20.0-alpha.3",
41
+ "@kurone-kito/vite-lib-config": "^0.20.0-alpha.3"
42
+ },
43
+ "peerDependencies": {
44
+ "xsea": "*"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "xsea": {
48
+ "optional": true
49
+ }
50
+ },
51
+ "engines": {
52
+ "node": "^20.11 || ^22 || >=24"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "scripts": {
58
+ "build": "vite build",
59
+ "clean": "rimraf -g \"*.tgz\" \"*.tsbuildinfo\" coverage dist LICENSE node_modules/.cache",
60
+ "dev": "vite build --watch",
61
+ "devPreinstall": "pnpm dlx mkdirp dist && pnpm run \"/devPreinstall:.+/\"",
62
+ "devPreinstall:builder": "pnpm dlx shx head -n 1 src/builder.mts > dist/builder.mjs",
63
+ "devPreinstall:cache": "pnpm dlx shx head -n 1 src/cache.mts > dist/cache.mjs",
64
+ "prebuild": "cpy --flat ../../LICENSE .",
65
+ "test": "vitest run --coverage"
66
+ }
67
+ }