Skip to content

Commit a073bea

Browse files
cinderblockclaude
andcommitted
Set up npm publishing: bundled CLIs, license attribution
Makes the package publishable as a CLI: - Add `bin` entries (ewd, ewe) pointing at bundled, node-runnable scripts in dist/, each with a `#!/usr/bin/env node` shebang. - `build.ts` bundles src/ewd.ts and src/ewe.ts via Bun with all dependencies inlined and CJS output, so the published package declares zero runtime dependencies (and doesn't drag the node-pkware git dependency along behind it). - Move the former runtime deps to devDependencies — they only matter for the bundle, which is produced at publish time. - `engines` now targets node >= 18 (what the bundled bins run on); bun stays the dev/build tool. - `files` allowlist ships dist/ + THIRD-PARTY-LICENSES.md; LICENSE and README are included automatically. - `scripts/collect-licenses.ts` walks the bundled runtime dependency closure (79 packages, all MIT/ISC/BSD/Zlib) and emits THIRD-PARTY-LICENSES.md so the bundle satisfies those terms. - Add an ISC LICENSE file (the package already declared ISC). - `prepublishOnly` runs check + typecheck + test + licenses + build. Verified by installing the packed tarball into a clean project and running the ewd bin end-to-end under plain node. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 774ca7d commit a073bea

6 files changed

Lines changed: 183 additions & 16 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
node_modules/
2-
samples/
2+
samples/
3+
dist/
4+
THIRD-PARTY-LICENSES.md

LICENSE

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ISC License
2+
3+
Copyright (c) 2021-2026 Cameron Tacklind <cameron@tacklind.com>
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

build.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Bundles the two CLIs into self-contained, node-runnable scripts under
3+
* `dist/`. All dependencies (winston, command-line-args, mdb-reader, the
4+
* node-pkware fork) are inlined, so the published package declares no
5+
* runtime dependencies. A `#!/usr/bin/env node` banner makes the output
6+
* directly executable as the `ewd` / `ewe` bin commands.
7+
*/
8+
9+
import { build } from 'bun';
10+
11+
const result = await build({
12+
entrypoints: ['src/ewd.ts', 'src/ewe.ts'],
13+
outdir: 'dist',
14+
target: 'node',
15+
format: 'cjs',
16+
banner: '#!/usr/bin/env node',
17+
});
18+
19+
if (!result.success) {
20+
for (const log of result.logs) console.error(log);
21+
process.exit(1);
22+
}
23+
24+
for (const out of result.outputs) {
25+
console.log(`built ${out.path}`);
26+
}

bun.lock

Lines changed: 3 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,37 @@
11
{
22
"name": "electronics-workbench-decoder",
3-
"version": "0.0.0",
3+
"version": "0.1.0",
44
"description": "Decode (ewd) and encode (ewe) compressed Electronics Workbench / Multisim / Ultiboard files",
55
"author": "Cameron Tacklind <cameron@tacklind.com>",
66
"license": "ISC",
7-
"main": "decode",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/cinderblock/EWD.git"
10+
},
11+
"homepage": "https://github.com/cinderblock/EWD#readme",
12+
"bugs": {
13+
"url": "https://github.com/cinderblock/EWD/issues"
14+
},
15+
"keywords": [
16+
"electronics-workbench",
17+
"multisim",
18+
"ultiboard",
19+
"ewprj",
20+
"ms14",
21+
"pkware",
22+
"implode",
23+
"national-instruments",
24+
"decoder",
25+
"cli"
26+
],
27+
"bin": {
28+
"ewd": "dist/ewd.js",
29+
"ewe": "dist/ewe.js"
30+
},
31+
"files": [
32+
"dist",
33+
"THIRD-PARTY-LICENSES.md"
34+
],
835
"scripts": {
936
"start": "bun run src/ewd.ts",
1037
"dev": "bun --watch run src/ewd.ts",
@@ -16,21 +43,22 @@
1643
"typecheck": "tsc --noEmit",
1744
"check": "biome check",
1845
"check:fix": "biome check --write",
19-
"format": "biome format --write"
46+
"format": "biome format --write",
47+
"build": "bun run build.ts",
48+
"licenses": "bun run scripts/collect-licenses.ts",
49+
"prepublishOnly": "bun run check && bun run typecheck && bun test && bun run licenses && bun run build"
2050
},
2151
"engines": {
22-
"bun": ">=1.0.0"
23-
},
24-
"dependencies": {
25-
"command-line-args": "^6.0.2",
26-
"node-pkware": "cinderblock/node-pkware#v5-fix",
27-
"winston": "^3.19.0"
52+
"node": ">=18"
2853
},
2954
"devDependencies": {
3055
"@biomejs/biome": "^2.4.15",
3156
"@types/bun": "^1.3.14",
3257
"@types/command-line-args": "^5.2.3",
58+
"command-line-args": "^6.0.2",
3359
"mdb-reader": "^3.2.0",
34-
"typescript": "^6.0.3"
60+
"node-pkware": "cinderblock/node-pkware#v5-fix",
61+
"typescript": "^6.0.3",
62+
"winston": "^3.19.0"
3563
}
3664
}

scripts/collect-licenses.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Generates THIRD-PARTY-LICENSES.md from the runtime dependency closure.
3+
*
4+
* The published CLIs bundle their runtime dependencies into `dist/`, which
5+
* strips the per-package license headers. To satisfy the (permissive) MIT /
6+
* ISC / BSD terms of those bundled packages, we collect each one's license
7+
* text into a single notices file shipped alongside the bundle.
8+
*
9+
* Roots are the packages actually imported by src/. Dev-only tooling
10+
* (biome, typescript, @types/*) is intentionally excluded — none of it is
11+
* bundled into dist/.
12+
*/
13+
14+
import { readdirSync, readFileSync } from 'node:fs';
15+
import { join } from 'node:path';
16+
17+
const ROOTS = ['node-pkware', 'command-line-args', 'winston', 'mdb-reader'];
18+
const NODE_MODULES = 'node_modules';
19+
20+
interface Pkg {
21+
name: string;
22+
version: string;
23+
license?: string;
24+
licenses?: { type: string }[];
25+
author?: unknown;
26+
dependencies?: Record<string, string>;
27+
homepage?: string;
28+
}
29+
30+
function readPkg(name: string): Pkg | undefined {
31+
try {
32+
return JSON.parse(readFileSync(join(NODE_MODULES, name, 'package.json'), 'utf8')) as Pkg;
33+
} catch {
34+
return undefined;
35+
}
36+
}
37+
38+
function readLicenseText(name: string): string | undefined {
39+
const dir = join(NODE_MODULES, name);
40+
let entries: string[];
41+
try {
42+
entries = readdirSync(dir);
43+
} catch {
44+
return undefined;
45+
}
46+
const file = entries.find(e => /^(licen[sc]e|copying)(\.|$)/i.test(e));
47+
if (!file) return undefined;
48+
try {
49+
return readFileSync(join(dir, file), 'utf8').trim();
50+
} catch {
51+
return undefined;
52+
}
53+
}
54+
55+
function licenseId(pkg: Pkg): string {
56+
if (typeof pkg.license === 'string') return pkg.license;
57+
if (pkg.licenses?.length) return pkg.licenses.map(l => l.type).join(', ');
58+
return 'UNKNOWN';
59+
}
60+
61+
// Resolve the transitive closure of runtime dependencies.
62+
const seen = new Set<string>();
63+
const queue = [...ROOTS];
64+
while (queue.length > 0) {
65+
const name = queue.shift() as string;
66+
if (seen.has(name)) continue;
67+
seen.add(name);
68+
const pkg = readPkg(name);
69+
if (pkg?.dependencies) queue.push(...Object.keys(pkg.dependencies));
70+
}
71+
72+
const names = [...seen].sort();
73+
74+
let unknown = 0;
75+
const sections: string[] = [];
76+
for (const name of names) {
77+
const pkg = readPkg(name);
78+
if (!pkg) continue;
79+
const id = licenseId(pkg);
80+
if (id === 'UNKNOWN') unknown++;
81+
const text = readLicenseText(name);
82+
const header = `## ${name}@${pkg.version} (${id})`;
83+
const link = pkg.homepage ? `\n${pkg.homepage}\n` : '';
84+
const body = text ? `\n\`\`\`\n${text}\n\`\`\`\n` : '\n_(no license file shipped in the package; see the SPDX id above)_\n';
85+
sections.push(`${header}\n${link}${body}`);
86+
}
87+
88+
const preamble = `# Third-party licenses
89+
90+
The \`ewd\` and \`ewe\` executables in \`dist/\` are bundled with the following
91+
third-party packages. Their license texts are reproduced below. This file is
92+
generated by \`scripts/collect-licenses.ts\`.
93+
94+
Bundled packages (${names.length}): ${names.join(', ')}.
95+
`;
96+
97+
await Bun.write('THIRD-PARTY-LICENSES.md', `${preamble}\n${sections.join('\n')}`);
98+
console.log(`Wrote THIRD-PARTY-LICENSES.md for ${names.length} packages (${unknown} with no SPDX id).`);

0 commit comments

Comments
 (0)