@lingva/build-tools 0.0.0-stage → 0.1.0
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.md +24 -0
- package/README.md +4 -2
- package/build-package.mjs +120 -0
- package/config.d.mts +4 -0
- package/config.mjs +9 -0
- package/package.json +32 -3
- package/protect-build.mjs +73 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Lingva SDK License 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) Yury Saukou. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is granted, without charge, to install, execute, and internally modify
|
|
6
|
+
this Lingva SDK package for the purpose of integrating applications with Lingva
|
|
7
|
+
or using its local localization features.
|
|
8
|
+
|
|
9
|
+
You may redistribute the unmodified package, or compiled portions of it, only as
|
|
10
|
+
a dependency or component of an application that provides substantial
|
|
11
|
+
functionality beyond the SDK itself. You may not sell, sublicense, republish, or
|
|
12
|
+
distribute the SDK as a standalone product; remove or alter copyright and
|
|
13
|
+
license notices; use the SDK to provide a competing localization platform or
|
|
14
|
+
SDK; or circumvent access controls, usage limits, or commercial entitlements of
|
|
15
|
+
Lingva services.
|
|
16
|
+
|
|
17
|
+
Access to hosted Lingva services, paid features, support, and self-hosted Lingva
|
|
18
|
+
software is governed separately and is not granted by this license.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING
|
|
24
|
+
FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Lingva build tools
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared package build lock, package-local tsdown launcher, and production source-map removal. Both Lingva repositories consume the same release. `LINGVA_DEV_BUILD=true` enables developer source maps; production builds remove map files and references.
|
|
4
|
+
|
|
5
|
+
Run `lingva-build` from a package directory. Import `defineLingvaPackageBuildConfig` from `@lingva/build-tools/config` for tsdown configuration.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const BUILD_LOCK_FILE = '.lingva-build.lock';
|
|
12
|
+
const BUILD_LOCK_RETRY_MS = 100;
|
|
13
|
+
const BUILD_LOCK_METADATA = 'owner.json';
|
|
14
|
+
const STALE_LOCK_METADATA_MS = 60_000;
|
|
15
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
function wait(durationMs) {
|
|
18
|
+
return new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function isProcessRunning(pid) {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
} catch (cause) {
|
|
26
|
+
return cause?.code !== 'ESRCH';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function clearStaleLock(lockPath) {
|
|
31
|
+
try {
|
|
32
|
+
const value = JSON.parse(await fs.readFile(path.join(lockPath, BUILD_LOCK_METADATA), 'utf8'));
|
|
33
|
+
|
|
34
|
+
if (!Number.isInteger(value.pid) || !(await isProcessRunning(value.pid))) {
|
|
35
|
+
await fs.rm(lockPath, { force: true, recursive: true });
|
|
36
|
+
}
|
|
37
|
+
} catch (cause) {
|
|
38
|
+
if (cause?.code === 'ENOENT') {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const stats = await fs.stat(lockPath).catch(() => null);
|
|
43
|
+
if (stats && Date.now() - stats.mtimeMs > STALE_LOCK_METADATA_MS) {
|
|
44
|
+
await fs.rm(lockPath, { force: true, recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function acquireBuildLock(lockPath) {
|
|
50
|
+
const token = randomUUID();
|
|
51
|
+
const value = JSON.stringify({ pid: process.pid, token });
|
|
52
|
+
|
|
53
|
+
while (true) {
|
|
54
|
+
try {
|
|
55
|
+
await fs.mkdir(lockPath);
|
|
56
|
+
await fs.writeFile(path.join(lockPath, BUILD_LOCK_METADATA), value, 'utf8');
|
|
57
|
+
return token;
|
|
58
|
+
} catch (cause) {
|
|
59
|
+
if (cause?.code !== 'EEXIST') {
|
|
60
|
+
throw cause;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await clearStaleLock(lockPath);
|
|
64
|
+
await wait(BUILD_LOCK_RETRY_MS);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function releaseBuildLock(lockPath, token) {
|
|
70
|
+
try {
|
|
71
|
+
const current = JSON.parse(await fs.readFile(path.join(lockPath, BUILD_LOCK_METADATA), 'utf8'));
|
|
72
|
+
if (current.token === token) {
|
|
73
|
+
await fs.rm(lockPath, { force: true, recursive: true });
|
|
74
|
+
}
|
|
75
|
+
} catch (cause) {
|
|
76
|
+
if (cause?.code !== 'ENOENT') {
|
|
77
|
+
throw cause;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function run(command, args) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const child = spawn(command, args, { cwd: process.cwd(), stdio: 'inherit' });
|
|
85
|
+
child.once('error', reject);
|
|
86
|
+
child.once('exit', (code, signal) => {
|
|
87
|
+
if (code === 0) {
|
|
88
|
+
resolve();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
reject(new Error(`${command} exited with ${signal ?? `code ${code}`}.`));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function runPackageBuild({ copyTemplates } = {}) {
|
|
98
|
+
const lockPath = path.join(process.cwd(), BUILD_LOCK_FILE);
|
|
99
|
+
const token = await acquireBuildLock(lockPath);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Launch the package's own CLI with Node: Windows cannot directly spawn
|
|
103
|
+
// a .cmd shim, and shell interpolation would break paths containing spaces.
|
|
104
|
+
const require = createRequire(path.join(process.cwd(), 'package.json'));
|
|
105
|
+
await run(process.execPath, [require.resolve('tsdown/run')]);
|
|
106
|
+
|
|
107
|
+
if (copyTemplates) await copyTemplates();
|
|
108
|
+
|
|
109
|
+
await run(process.execPath, [path.join(scriptDirectory, 'protect-build.mjs'), 'dist']);
|
|
110
|
+
} finally {
|
|
111
|
+
await releaseBuildLock(lockPath, token);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
116
|
+
void runPackageBuild().catch((cause) => {
|
|
117
|
+
console.error(cause);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
});
|
|
120
|
+
}
|
package/config.d.mts
ADDED
package/config.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { defineConfig } from 'tsdown';
|
|
3
|
+
|
|
4
|
+
const isDeveloperBuild = process.env.LINGVA_DEV_BUILD === 'true';
|
|
5
|
+
|
|
6
|
+
/** @param {import('tsdown').Options} config */
|
|
7
|
+
export function defineLingvaPackageBuildConfig(config) {
|
|
8
|
+
return defineConfig({ ...config, clean: true, minify: !isDeveloperBuild, sourcemap: isDeveloperBuild });
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingva/build-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared protected package builds for Lingva workspaces",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"files": [
|
|
8
|
+
"build-package.mjs",
|
|
9
|
+
"protect-build.mjs",
|
|
10
|
+
"config.mjs",
|
|
11
|
+
"config.d.mts"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"lingva-build": "./build-package.mjs"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./build": "./build-package.mjs",
|
|
18
|
+
"./config": {
|
|
19
|
+
"types": "./config.d.mts",
|
|
20
|
+
"import": "./config.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"tsdown": "^0.21.0"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"provenance": true
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/usausoft/lingva.git",
|
|
33
|
+
"directory": "packages/build-tools"
|
|
34
|
+
}
|
|
6
35
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
async function collectFiles(targetPath) {
|
|
8
|
+
const stats = await fs.stat(targetPath);
|
|
9
|
+
|
|
10
|
+
if (stats.isFile()) {
|
|
11
|
+
return [targetPath];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!stats.isDirectory()) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
19
|
+
const files = await Promise.all(
|
|
20
|
+
entries.map(async (entry) => {
|
|
21
|
+
const entryPath = path.join(targetPath, entry.name);
|
|
22
|
+
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
return collectFiles(entryPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (entry.isFile()) {
|
|
28
|
+
return [entryPath];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [];
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return files.flat();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function stripSourceMapReference(filePath) {
|
|
39
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
40
|
+
content = content.replace(/\n\/\/# sourceMappingURL=.*$/u, '');
|
|
41
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
if (process.env.LINGVA_DEV_BUILD === 'true') {
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const distPath = process.argv[2];
|
|
50
|
+
|
|
51
|
+
if (!distPath) {
|
|
52
|
+
console.error('Usage: node scripts/protect-build.mjs <dist-dir>');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const absoluteDistPath = path.resolve(process.cwd(), distPath);
|
|
57
|
+
const files = await collectFiles(absoluteDistPath);
|
|
58
|
+
|
|
59
|
+
await Promise.all(
|
|
60
|
+
files.map(async (filePath) => {
|
|
61
|
+
if (filePath.endsWith('.map')) {
|
|
62
|
+
await fs.rm(filePath, { force: true });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (/\.(?:[cm]?[jt]s)$/u.test(filePath)) {
|
|
67
|
+
await stripSourceMapReference(filePath);
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void main();
|