@temir.ra/create-ts-lib 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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Version 0
2
+
3
+ ## 0.1.0
4
+
5
+ 1. First version of the template.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Temir Ra
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,358 @@
1
+ # Introduction
2
+
3
+ A template for TypeScript libraries distributed via npm-compatible registries. Provides TypeScript configuration, build tooling for ESM and bundled outputs, and build metadata generation.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Quick Start](#quick-start)
8
+ 2. [Documentation](#documentation)
9
+ 1. [`tsconfig.json` and `tsconfig.build.json`](#tsconfigjson-and-tsconfigbuildjson)
10
+ 2. [`package.json`](#packagejson)
11
+ 3. [Script `scripts/buildinfo.ts`](#script-scriptsbuildinfots)
12
+ 4. [Script `scripts/build-lib-bundle.ts`](#script-scriptsbuild-lib-bundlets)
13
+ 5. [CDN Map `scripts/cdn-rewrite-map.json`](#cdn-map-scriptscdn-rewrite-mapjson)
14
+ 6. [`src/urls.ts`](#srcurlsts)
15
+ 7. [`src/dev.ts`](#srcdevts)
16
+ 8. [`CLAUDE.md` / `AGENTS.md`](#claudemd--agentsmd)
17
+ 3. [DevOps](#devops)
18
+ 1. [Build](#build)
19
+ 2. [Change Management](#change-management)
20
+ 3. [Publish](#publish)
21
+ 1. [npmjs.org](#npmjsorg)
22
+ 2. [Custom registry](#custom-registry)
23
+
24
+ # Quick Start
25
+
26
+ ```bash
27
+ # placeholder:
28
+ # <PACKAGE: <PACKAGE>
29
+
30
+ bun create --no-install --no-git "@temir.ra/ts-lib" <PACKAGE>
31
+ cd <PACKAGE>
32
+ bun install
33
+ ```
34
+
35
+ # Documentation
36
+
37
+ ## `tsconfig.json` and `tsconfig.build.json`
38
+
39
+ ```json
40
+ {
41
+
42
+ "compilerOptions": {
43
+
44
+ // ECMAScript version of emitted output
45
+ "target": "ES2022",
46
+
47
+ // output module format; ESNext passes ES module syntax through unchanged
48
+ "module": "ESNext",
49
+
50
+ // type definitions for built-in APIs
51
+ "lib": [
52
+ "ES2022", // standard JavaScript runtime APIs
53
+ "DOM" // browser globals for bundled output
54
+ ],
55
+
56
+ // module resolution strategy; bundler mode allows omitting file extensions in imports
57
+ // tsconfig.build.json overrides this to nodenext for strict ESM compliance
58
+ "moduleResolution": "bundler",
59
+
60
+ // enables all strict type-checking options
61
+ "strict": true,
62
+
63
+ // enforces import type for type-only imports; emitted module syntax matches source exactly
64
+ "verbatimModuleSyntax": true,
65
+
66
+ // array indexing and index signature access returns T | undefined instead of T
67
+ "noUncheckedIndexedAccess": true,
68
+
69
+ // distinguishes absent optional properties from those explicitly set to undefined
70
+ "exactOptionalPropertyTypes": true,
71
+
72
+ // requires explicit override keyword when overriding base class methods
73
+ "noImplicitOverride": true,
74
+
75
+ // requires explicit types on all exported declarations; enables parallel .d.ts generation by external tools
76
+ "isolatedDeclarations": true,
77
+
78
+ // allows default imports from CommonJS modules
79
+ "esModuleInterop": true,
80
+
81
+ // enables project references and incremental builds via *.tsbuildinfo
82
+ "composite": true,
83
+
84
+ // do not type-check `.d.ts` files in `node_modules/`
85
+ "skipLibCheck": true,
86
+
87
+ // enforce consistent casing across import statements
88
+ "forceConsistentCasingInFileNames": true,
89
+
90
+ // allows importing JSON files as typed modules
91
+ "resolveJsonModule": true,
92
+
93
+ // emit .d.ts declaration files
94
+ "declaration": true,
95
+ // emit .d.ts.map files mapping declarations back to source
96
+ "declarationMap": true,
97
+
98
+ // emit only .d.ts files, no JavaScript; overridden in tsconfig.build.json
99
+ "emitDeclarationOnly": true,
100
+
101
+ // output directory for emitted files
102
+ "outDir": "./dist",
103
+
104
+ // root directory mirrored into outDir; set to project root during development, overridden to src/ in tsconfig.build.json
105
+ "rootDir": ".",
106
+
107
+ },
108
+
109
+ // includes src/, tests/, and scripts/ for type-checking and IDE support; overridden in tsconfig.build.json
110
+ "include": [
111
+ "src/**/*.ts",
112
+ "tests/**/*.ts",
113
+ "scripts/**/*.ts",
114
+ "scripts/**/*.json"
115
+ ],
116
+ "exclude": [
117
+ "node_modules",
118
+ "dist"
119
+ ]
120
+
121
+ }
122
+ ```
123
+
124
+ `tsconfig.json` is the development configuration. `tsconfig.build.json` extends it, narrowing scope to `src/` and enabling JavaScript output for distribution.
125
+
126
+ `declarationMap: true` enables go-to-definition for npm/bun consumers. For this to work, the original `.ts` source files must be accessible to the consumer. Consider adding `src/` to the `files` field in `package.json`.
127
+
128
+ ```json
129
+ {
130
+
131
+ "extends": "./tsconfig.json",
132
+
133
+ "compilerOptions": {
134
+
135
+ // narrows root to src/ for distribution output
136
+ "rootDir": "./src",
137
+
138
+ // nodenext enforces strict ESM compliance; imports must use explicit .js file extensions
139
+ "module": "nodenext",
140
+ "moduleResolution": "nodenext",
141
+
142
+ // emit both JavaScript files and declaration files for distribution
143
+ "emitDeclarationOnly": false,
144
+
145
+ },
146
+
147
+ "include": [
148
+ "src/**/*.ts"
149
+ ],
150
+ "exclude": [
151
+ "src/dev.ts",
152
+ "node_modules",
153
+ "dist",
154
+ "tests",
155
+ "scripts"
156
+ ]
157
+
158
+ }
159
+ ```
160
+
161
+ ## `package.json`
162
+
163
+ ```json
164
+ {
165
+ "name": "",
166
+ "version": "0.0.0",
167
+
168
+ "description": "",
169
+ "author": "",
170
+ "license": "",
171
+
172
+ "keywords": ["typescript"],
173
+
174
+ "repository": {
175
+ "type": "git",
176
+ "url": ""
177
+ },
178
+
179
+ // treats all .js files as ES modules; use .cjs extension for CommonJS files
180
+ "type": "module",
181
+
182
+ // no module has side effects; enables full tree-shaking by bundlers
183
+ // set to an array of file paths if some modules do have side effects, e.g. ["./src/polyfill.ts"]
184
+ "sideEffects": false,
185
+
186
+ // package entry points by consumer type
187
+ // "entrypoint" — custom condition; used by the bundle script to locate the source entry point
188
+ // "types" — TypeScript consumers; resolves to the declaration files; must precede "import" so TypeScript matches it before the JS condition
189
+ // "browser" — browser bundler consumers; resolves to the bundled output
190
+ // "import" — ESM consumers; resolves to the compiled module output
191
+ "exports": {
192
+ ".": {
193
+ "entrypoint": "./src/index.ts",
194
+ "types": "./dist/index.d.ts",
195
+ "browser": "./dist/index.bundle.js",
196
+ "import": "./dist/index.js"
197
+ }
198
+ },
199
+
200
+ // package-internal import alias resolved natively by Node.js and Bun at runtime; key must start with #
201
+ // for use in dev.ts, tests/, and scripts/ only — NOT in library source files compiled by tsconfig.build.json
202
+ // the .js extension is required in import statements (nodenext compliance);
203
+ // the runtime maps it to the actual .ts source file via this field
204
+ "imports": {
205
+ "#src/*.js": "./src/*.ts"
206
+ },
207
+
208
+ // CLI entry point; omit if the package is not a CLI tool
209
+ "bin": "./dist/cli.bundle.js",
210
+
211
+ // files to include in the published package
212
+ "files": [
213
+ "dist",
214
+ "CHANGELOG.md",
215
+ "buildinfo.txt"
216
+ ],
217
+
218
+ "scripts": {
219
+
220
+ "clean:dist": "rm -rf dist/",
221
+ "clean:tsbuildinfo": "rm -f tsconfig.build.tsbuildinfo",
222
+ "clean": "bun run clean:dist && bun run clean:tsbuildinfo",
223
+
224
+ // lifecycle hook; runs automatically before "build"; generates buildinfo.txt
225
+ "prebuild": "bun run scripts/buildinfo.ts",
226
+
227
+ "tests": "bun test",
228
+
229
+ "build": "bun run build:lib && bun run build:lib-bundle",
230
+
231
+ "build:lib": "tsc --project tsconfig.build.json",
232
+ // bundles the library into ESM and IIFE formats for distribution
233
+ "build:lib-bundle": "bun run scripts/build-lib-bundle.ts",
234
+
235
+ "build:cli-bundle": "bun build src/cli.ts --entry-naming \"[dir]/[name].bundle.[ext]\" --outdir dist --target node --format esm --minify --sourcemap=external",
236
+
237
+ "typecheck": "tsc --noEmit",
238
+
239
+ // runs src/dev.ts in watch mode
240
+ "dev": "bun run --watch src/dev.ts"
241
+
242
+ },
243
+ "devDependencies": {
244
+ "@types/bun": "latest",
245
+ "typescript": "^5.9.3"
246
+ }
247
+ }
248
+ ```
249
+
250
+ ## Script `scripts/buildinfo.ts`
251
+
252
+ Generates `buildinfo.txt` containing the version from `package.json` and the git commit hash (if available). Included in the published package for build traceability.
253
+
254
+ ## Script `scripts/build-lib-bundle.ts`
255
+
256
+ Bundles the library to ESM and IIFE formats. Entry points are resolved from the `entrypoint` condition in the `exports` field of `package.json`. Import specifiers can be rewritten to CDN URLs via `scripts/cdn-rewrite-map.json`.
257
+
258
+ ## CDN Map `scripts/cdn-rewrite-map.json`
259
+
260
+ Maps import specifiers to CDN URLs. During bundling, matching specifiers in source files are rewritten to their CDN equivalents. `<VERSION>` in a URL is replaced with the version of the matching package from `package.json`.
261
+
262
+ ```json
263
+ {
264
+ "import-specifier": "https://cdn.jsdelivr.net/npm/package-name@<VERSION>/dist/index.bundle.js"
265
+ }
266
+ ```
267
+
268
+ ## `"bin"` field in `package.json`
269
+
270
+ For CLI packages, add the following to `package.json`:
271
+
272
+ ```json
273
+ {
274
+ "exports": ...,
275
+ "bin": "./dist/cli.bundle.js",
276
+ "files": ...,
277
+ "scripts": {
278
+ ...,
279
+ "build": "... && bun run build:cli-bundle",
280
+ ...,
281
+ "build:cli-bundle": "bun build src/cli.ts --entry-naming \"[dir]/[name].bundle.[ext]\" --outdir dist --target node --format esm --minify --sourcemap=external",
282
+ ...,
283
+ }
284
+ }
285
+ ```
286
+
287
+ ## `src/urls.ts`
288
+
289
+ Resolves `CHANGELOG.md` and `buildinfo.txt` relative to `dist/` at runtime using `import.meta.url`. Provides stable references to published package assets regardless of install location.
290
+
291
+ ## `src/dev.ts`
292
+
293
+ Development scratchpad — not published, excluded from `tsconfig.build.json`. Run with `bun run dev` in watch mode. Use it to manually test and explore library code during development.
294
+
295
+ ## `CLAUDE.md` / `AGENTS.md`
296
+
297
+ AI assistant context files. Provide project layout, commands, and architecture notes. `AGENTS.md` references `CLAUDE.md`. The template ships two pairs:
298
+
299
+ - Root pair — describes the template package itself (scaffolding tool, CLI, how `template/` maps to generated projects)
300
+ - `template/` pair — describes the generated library project; update to reflect the specific library being developed
301
+
302
+ # DevOps
303
+
304
+ ## Build
305
+
306
+ ```bash
307
+ # remove dist/ and tsconfig.build.tsbuildinfo
308
+ bun run clean
309
+
310
+ # remove dist/ only
311
+ bun run clean:dist
312
+
313
+ # remove tsconfig.build.tsbuildinfo only
314
+ bun run clean:tsbuildinfo
315
+
316
+ # compile + bundle
317
+ bun run build
318
+ ```
319
+
320
+ ## Change Management
321
+
322
+ 1. Create a new branch for the change.
323
+ 2. Make the changes and commit.
324
+ 3. Bump the version in [`package.json`](package.json).
325
+ 4. Add an entry for the new version in [`CHANGELOG.md`](CHANGELOG.md).
326
+ 5. Run `bun run build`.
327
+ 6. Pull request the branch and publish.
328
+
329
+ ## Publish
330
+
331
+ ### `npmjs.org`
332
+
333
+ ⚠️ Authenticate first: run `npm login` or configure `~/.npmrc` or `bunfig.toml` with an auth token.
334
+
335
+ ```powershell
336
+ bun publish --registry https://registry.npmjs.org/ --access public
337
+ ```
338
+
339
+ ### Custom registry
340
+
341
+ ```bash
342
+ # placeholder:
343
+ # <SCOPE_WITHOUT_AT: <SCOPE_WITHOUT_AT>
344
+ # <REGISTRY_URL: <REGISTRY_URL>
345
+ # <BUN_PUBLISH_AUTH_TOKEN: <BUN_PUBLISH_AUTH_TOKEN>
346
+ ```
347
+
348
+ `bunfig.toml`:
349
+
350
+ ```toml
351
+ [install.scopes]
352
+ <SCOPE_WITHOUT_AT> = { url = "<REGISTRY_URL>", token = "$BUN_PUBLISH_AUTH_TOKEN" }
353
+ ```
354
+
355
+ ```powershell
356
+ $env:BUN_PUBLISH_AUTH_TOKEN = "<BUN_PUBLISH_AUTH_TOKEN>"
357
+ bun publish
358
+ ```
package/buildinfo.txt ADDED
@@ -0,0 +1 @@
1
+ 0.1.0+d78d3df
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import{cpSync as o,readFileSync as U,renameSync as f,writeFileSync as d}from"fs";import{resolve as t,basename as u}from"path";import{fileURLToPath as a}from"url";var n=new URL(".",import.meta.url),l=new URL("../CHANGELOG.md",n),p=new URL("../buildinfo.txt",n),m=new URL("../template",n);try{let r=process.argv[2];if(!r)throw Error("Destination path is required. Usage: `bun/npm create <template> <destination>`");let e=t(process.cwd(),r),i=u(e),g=t(a(m));o(g,e,{recursive:!0}),o(t(a(l)),t(e,"CHANGELOG-template.md")),o(t(a(p)),t(e,"buildinfo-template.txt"));let s=t(e,"package.json"),c=JSON.parse(U(s,"utf-8"));c.name=i,d(s,JSON.stringify(c,null,2)),f(t(e,"gitignore"),t(e,".gitignore")),console.log(`Template has been successfully instantiated at '${e}' with package name '${i}'.`)}catch(r){let e=r instanceof Error?r:Error(String(r));console.error("Error:",e.message),process.exit(1)}
3
+
4
+ //# debugId=683E20367A2ED95764756E2164756E21
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["..\\src\\cli.ts", "..\\src\\urls.ts"],
4
+ "sourcesContent": [
5
+ "#!/usr/bin/env node\r\n\r\nimport { cpSync, readFileSync, renameSync, writeFileSync } from 'fs';\r\nimport { resolve, basename } from 'path';\r\nimport { fileURLToPath } from 'url';\r\nimport { buildinfoUrl, changelogUrl, templateUrl } from '#src/urls.js';\r\n\r\n\r\ntry {\r\n\r\n const dest = process.argv[2];\r\n if (!dest) throw new Error('Destination path is required. Usage: `bun/npm create <template> <destination>`');\r\n\r\n const destinationPath = resolve(process.cwd(), dest);\r\n const packageName = basename(destinationPath);\r\n\r\n const templatePath = resolve(fileURLToPath(templateUrl));\r\n\r\n cpSync(templatePath, destinationPath, { recursive: true });\r\n cpSync(resolve(fileURLToPath(changelogUrl)), resolve(destinationPath, 'CHANGELOG-template.md'));\r\n cpSync(resolve(fileURLToPath(buildinfoUrl)), resolve(destinationPath, 'buildinfo-template.txt'));\r\n\r\n const packageManifestPath = resolve(destinationPath, 'package.json');\r\n const packageManifest = JSON.parse(readFileSync(packageManifestPath, 'utf-8'));\r\n packageManifest.name = packageName;\r\n writeFileSync(packageManifestPath, JSON.stringify(packageManifest, null, 2));\r\n\r\n renameSync(resolve(destinationPath, 'gitignore'), resolve(destinationPath, '.gitignore'));\r\n\r\n console.log(`Template has been successfully instantiated at '${destinationPath}' with package name '${packageName}'.`);\r\n\r\n}\r\ncatch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n console.error('Error:', err.message);\r\n process.exit(1);\r\n}\r\n",
6
+ "export const distUrl: URL = new URL('.', import.meta.url);\r\nexport const changelogUrl: URL = new URL('../CHANGELOG.md', distUrl);\r\nexport const buildinfoUrl: URL = new URL('../buildinfo.txt', distUrl);\r\nexport const templateUrl: URL = new URL('../template', distUrl);\r\n"
7
+ ],
8
+ "mappings": ";AAEA,iBAAS,kBAAQ,gBAAc,mBAAY,WAC3C,kBAAS,cAAS,aAClB,wBAAS,YCJF,IAAM,EAAe,IAAI,IAAI,IAAK,YAAY,GAAG,EAC3C,EAAoB,IAAI,IAAI,kBAAmB,CAAO,EACtD,EAAoB,IAAI,IAAI,mBAAoB,CAAO,EACvD,EAAmB,IAAI,IAAI,cAAe,CAAO,EDK9D,GAAI,CAEA,IAAM,EAAO,QAAQ,KAAK,GAC1B,GAAI,CAAC,EAAM,MAAU,MAAM,gFAAgF,EAE3G,IAAM,EAAkB,EAAQ,QAAQ,IAAI,EAAG,CAAI,EAC7C,EAAc,EAAS,CAAe,EAEtC,EAAe,EAAQ,EAAc,CAAW,CAAC,EAEvD,EAAO,EAAc,EAAiB,CAAE,UAAW,EAAK,CAAC,EACzD,EAAO,EAAQ,EAAc,CAAY,CAAC,EAAG,EAAQ,EAAiB,uBAAuB,CAAC,EAC9F,EAAO,EAAQ,EAAc,CAAY,CAAC,EAAG,EAAQ,EAAiB,wBAAwB,CAAC,EAE/F,IAAM,EAAsB,EAAQ,EAAiB,cAAc,EAC7D,EAAkB,KAAK,MAAM,EAAa,EAAqB,OAAO,CAAC,EAC7E,EAAgB,KAAO,EACvB,EAAc,EAAqB,KAAK,UAAU,EAAiB,KAAM,CAAC,CAAC,EAE3E,EAAW,EAAQ,EAAiB,WAAW,EAAG,EAAQ,EAAiB,YAAY,CAAC,EAExF,QAAQ,IAAI,mDAAmD,yBAAuC,KAAe,EAGzH,MAAO,EAAO,CACV,IAAM,EAAM,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,EACpE,QAAQ,MAAM,SAAU,EAAI,OAAO,EACnC,QAAQ,KAAK,CAAC",
9
+ "debugId": "683E20367A2ED95764756E2164756E21",
10
+ "names": []
11
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { cpSync, readFileSync, renameSync, writeFileSync } from 'fs';
3
+ import { resolve, basename } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { buildinfoUrl, changelogUrl, templateUrl } from '#src/urls.js';
6
+ try {
7
+ const dest = process.argv[2];
8
+ if (!dest)
9
+ throw new Error('Destination path is required. Usage: `bun/npm create <template> <destination>`');
10
+ const destinationPath = resolve(process.cwd(), dest);
11
+ const packageName = basename(destinationPath);
12
+ const templatePath = resolve(fileURLToPath(templateUrl));
13
+ cpSync(templatePath, destinationPath, { recursive: true });
14
+ cpSync(resolve(fileURLToPath(changelogUrl)), resolve(destinationPath, 'CHANGELOG-template.md'));
15
+ cpSync(resolve(fileURLToPath(buildinfoUrl)), resolve(destinationPath, 'buildinfo-template.txt'));
16
+ const packageManifestPath = resolve(destinationPath, 'package.json');
17
+ const packageManifest = JSON.parse(readFileSync(packageManifestPath, 'utf-8'));
18
+ packageManifest.name = packageName;
19
+ writeFileSync(packageManifestPath, JSON.stringify(packageManifest, null, 2));
20
+ renameSync(resolve(destinationPath, 'gitignore'), resolve(destinationPath, '.gitignore'));
21
+ console.log(`Template has been successfully instantiated at '${destinationPath}' with package name '${packageName}'.`);
22
+ }
23
+ catch (error) {
24
+ const err = error instanceof Error ? error : new Error(String(error));
25
+ console.error('Error:', err.message);
26
+ process.exit(1);
27
+ }
package/dist/urls.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare const distUrl: URL;
2
+ export declare const changelogUrl: URL;
3
+ export declare const buildinfoUrl: URL;
4
+ export declare const templateUrl: URL;
5
+ //# sourceMappingURL=urls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../src/urls.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,EAAE,GAAmC,CAAC;AAC1D,eAAO,MAAM,YAAY,EAAE,GAAyC,CAAC;AACrE,eAAO,MAAM,YAAY,EAAE,GAA0C,CAAC;AACtE,eAAO,MAAM,WAAW,EAAE,GAAqC,CAAC"}
package/dist/urls.js ADDED
@@ -0,0 +1,4 @@
1
+ export const distUrl = new URL('.', import.meta.url);
2
+ export const changelogUrl = new URL('../CHANGELOG.md', distUrl);
3
+ export const buildinfoUrl = new URL('../buildinfo.txt', distUrl);
4
+ export const templateUrl = new URL('../template', distUrl);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@temir.ra/create-ts-lib",
3
+ "version": "0.1.0",
4
+ "description": "Typescript library template",
5
+ "author": "temir.ra",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "typescript",
9
+ "template",
10
+ "library",
11
+ "bun"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://git.temirs.cloud/trs/create-ts-lib.git"
16
+ },
17
+ "type": "module",
18
+ "sideEffects": false,
19
+ "imports": {
20
+ "#src/*.js": "./src/*.ts"
21
+ },
22
+ "bin": "./dist/cli.bundle.js",
23
+ "files": [
24
+ "dist",
25
+ "CHANGELOG.md",
26
+ "buildinfo.txt",
27
+ "template"
28
+ ],
29
+ "scripts": {
30
+ "clean:dist": "rm -rf dist/",
31
+ "clean:tsbuildinfo": "rm -f tsconfig.build.tsbuildinfo",
32
+ "clean": "bun run clean:dist && bun run clean:tsbuildinfo",
33
+ "prebuild": "bun run scripts/buildinfo.ts",
34
+ "tests": "bun test",
35
+ "build": "bun run build:lib && bun run build:cli-bundle",
36
+ "build:lib": "tsc --project tsconfig.build.json",
37
+ "build:cli-bundle": "bun build src/cli.ts --entry-naming \"[dir]/[name].bundle.[ext]\" --outdir dist --target node --format esm --minify --sourcemap=external",
38
+ "typecheck": "tsc --noEmit",
39
+ "dev": "bun run --watch src/dev.ts"
40
+ },
41
+ "devDependencies": {
42
+ "@types/bun": "latest",
43
+ "typescript": "^5.9.3"
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ See [CLAUDE.md](CLAUDE.md).
@@ -0,0 +1,5 @@
1
+ # Version 0
2
+
3
+ ## 0.0.0
4
+
5
+ 1. Versioning initialized.
@@ -0,0 +1,132 @@
1
+ # <package-name>
2
+
3
+ A Bun TypeScript library.
4
+
5
+ ## Project Layout
6
+
7
+ ```
8
+ <package-name>/
9
+ ├── src/
10
+ │ ├── index.ts # Library entry point (public API)
11
+ │ ├── dev.ts # Development scratchpad — run with `bun run dev`
12
+ │ └── urls.ts # URL helpers (changelog, buildinfo)
13
+ ├── scripts/
14
+ │ ├── buildinfo.ts # Writes version + git hash to buildinfo.txt
15
+ │ ├── build-lib-bundle.ts # Bundles the library for ESM + CDN distribution
16
+ │ └── cdn-rewrite-map.json # CDN import rewrite rules (empty by default)
17
+ ├── tests/ # Unit tests (Bun test runner)
18
+ ├── dist/ # Build output (gitignored)
19
+ ├── buildinfo.txt # Written during build; included in published package
20
+ ├── CHANGELOG.md # Version history
21
+ ├── CLAUDE.md # This file — AI context for the library project
22
+ └── AGENTS.md # References CLAUDE.md
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ ```bash
28
+ bun install # Install dependencies
29
+ bun run build # Compile + bundle the library
30
+ bun run tests # Run unit tests
31
+ bun run typecheck # Type-check without emitting
32
+ bun run dev # Run src/dev.ts in watch mode
33
+ bun run clean # Remove dist/ and tsconfig.build.tsbuildinfo
34
+ bun run clean:dist # Remove dist/ only
35
+ bun run clean:tsbuildinfo # Remove tsconfig.build.tsbuildinfo only
36
+ ```
37
+
38
+ ## Writing Code
39
+
40
+ ### Where things go
41
+
42
+ | Path | Purpose |
43
+ |------|---------|
44
+ | `src/index.ts` | Public API — the only entry point consumers see; export everything public from here |
45
+ | `src/*.ts` | Library source modules — add new modules here, re-export from `index.ts` |
46
+ | `src/dev.ts` | Development scratchpad — excluded from build, never published; use for manual testing |
47
+ | `tests/*.test.ts` | Unit tests — Bun test runner |
48
+
49
+ ### Imports within `src/`
50
+
51
+ `tsconfig.build.json` uses `moduleResolution: nodenext`, so imports between library source files must use explicit `.js` extensions:
52
+
53
+ ```ts
54
+ import { helper } from "./helper.js" // ✓
55
+ import { helper } from "./helper" // ✗ — fails under nodenext
56
+ ```
57
+
58
+ The `#src/*.js` package alias is available in `dev.ts`, `tests/`, and `scripts/`, but **not** in library source files — tsc emits the specifier verbatim and it would break in published unbundled output.
59
+
60
+ ### Explicit return types (`isolatedDeclarations`)
61
+
62
+ All exported declarations require explicit type annotations so tsc can emit `.d.ts` files without cross-file inference:
63
+
64
+ ```ts
65
+ export function greet(name: string): string { ... } // ✓
66
+ export function greet(name: string) { ... } // ✗ — implicit return type
67
+ ```
68
+
69
+ ### Type-only imports (`verbatimModuleSyntax`)
70
+
71
+ Use `import type` for imports that are only used as types:
72
+
73
+ ```ts
74
+ import type { Foo } from "./foo.js" // ✓ — type-only import
75
+ import { type Foo, bar } from "./foo.js" // ✓ — mixed
76
+ import { Foo } from "./foo.js" // ✗ — if Foo is only used as a type
77
+ ```
78
+
79
+ ### Tests
80
+
81
+ Tests live in `tests/` and use Bun's built-in runner:
82
+
83
+ ```ts
84
+ import { describe, test, expect } from "bun:test"
85
+
86
+ describe("greet", () => {
87
+ test("returns greeting", () => {
88
+ expect(greet("world")).toBe("hello, world")
89
+ })
90
+ })
91
+ ```
92
+
93
+ Run with `bun run tests`.
94
+
95
+ ## TypeScript Configuration
96
+
97
+ Two tsconfig files serve different purposes:
98
+
99
+ | File | Purpose |
100
+ |------|---------|
101
+ | `tsconfig.json` | Development — no emit, includes `src/`, `tests/`, `scripts/` |
102
+ | `tsconfig.build.json` | Build — emits JS + `.d.ts`, `src/` only (excludes `dev.ts`, tests, scripts) |
103
+
104
+ `bun run build:lib` uses `tsconfig.build.json`. During development the runtime handles TypeScript directly so no compilation step is needed.
105
+
106
+ Both configs enable an extended strict type-safety profile beyond `strict`: `verbatimModuleSyntax`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride`, `isolatedDeclarations`.
107
+
108
+ ## Build Outputs
109
+
110
+ `bun run build` runs two steps in sequence:
111
+
112
+ 1. **`build:lib`** — `tsc --project tsconfig.build.json` → `dist/*.js` + `dist/*.d.ts`
113
+ 2. **`build:lib-bundle`** — `scripts/build-lib-bundle.ts` → `dist/index.bundle.js` (ESM, minified) + `dist/index.iife.js` (IIFE, minified)
114
+
115
+ The `exports` field in `package.json` maps consumers to the right file:
116
+
117
+ | Condition | File |
118
+ |-----------|------|
119
+ | `types` | `dist/index.d.ts` |
120
+ | `browser` | `dist/index.bundle.js` |
121
+ | `import` | `dist/index.js` |
122
+
123
+ ## Package Configuration
124
+
125
+ Key `package.json` fields:
126
+
127
+ - `"sideEffects": false` — enables full tree-shaking by bundlers
128
+ - `"imports": { "#src/*.js": "./src/*.ts" }` — package-internal alias for `src/`; use as `import { foo } from "#src/module.js"`. **Only for `dev.ts`, `tests/`, and `scripts/`** — not for library source files compiled by `tsconfig.build.json`, as tsc emits the specifier verbatim and it would break in published unbundled output.
129
+
130
+ ## Publishing
131
+
132
+ See [README.md](README.md) for registry setup and publish commands.
@@ -0,0 +1,81 @@
1
+ # Introduction
2
+
3
+ *&lt;INTRO TEXT&gt;*
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Quick Start](#quick-start)
8
+ 2. [Documentation](#documentation)
9
+ 3. [DevOps](#devops)
10
+ 1. [Change Management](#change-management)
11
+ 2. [Publish](#publish)
12
+
13
+ # Quick Start
14
+
15
+ ```bash
16
+ # remove dist/ and tsconfig.build.tsbuildinfo
17
+ bun run clean
18
+
19
+ # remove dist/ only
20
+ bun run clean:dist
21
+
22
+ # remove tsconfig.build.tsbuildinfo only
23
+ bun run clean:tsbuildinfo
24
+
25
+ # compile + bundle
26
+ bun run build
27
+
28
+ # run tests
29
+ bun run tests
30
+
31
+ # run src/dev.ts in watch mode
32
+ bun run dev
33
+ ```
34
+
35
+ **Publish** — see [Publish](#publish).
36
+
37
+ # Documentation
38
+
39
+ *&lt;DOCUMENTATION&gt;*
40
+
41
+ # DevOps
42
+
43
+ ## Change Management
44
+
45
+ 1. Create a new branch for the change.
46
+ 2. Make the changes and commit.
47
+ 3. Bump the version in [`package.json`](package.json).
48
+ 4. Add an entry for the new version in [`CHANGELOG.md`](CHANGELOG.md).
49
+ 5. Run `bun run build`.
50
+ 6. Pull request the branch and publish.
51
+
52
+ ## Publish
53
+
54
+ ### `npmjs.org`
55
+
56
+ ⚠️ Authenticate first: run `npm login` or configure `~/.npmrc` or `bunfig.toml` with an auth token.
57
+
58
+ ```powershell
59
+ bun publish --registry https://registry.npmjs.org/ --access public
60
+ ```
61
+
62
+ ### Custom registry
63
+
64
+ ```bash
65
+ # placeholder:
66
+ # <SCOPE_WITHOUT_AT: <SCOPE_WITHOUT_AT>
67
+ # <REGISTRY_URL: <REGISTRY_URL>
68
+ # <BUN_PUBLISH_AUTH_TOKEN: <BUN_PUBLISH_AUTH_TOKEN>
69
+ ```
70
+
71
+ `bunfig.toml`:
72
+
73
+ ```toml
74
+ [install.scopes]
75
+ <SCOPE_WITHOUT_AT> = { url = "<REGISTRY_URL>", token = "$BUN_PUBLISH_AUTH_TOKEN" }
76
+ ```
77
+
78
+ ```powershell
79
+ $env:BUN_PUBLISH_AUTH_TOKEN = "<BUN_PUBLISH_AUTH_TOKEN>"
80
+ bun publish
81
+ ```
@@ -0,0 +1,4 @@
1
+ dist
2
+ node_modules
3
+ buildinfo.txt
4
+ *.tsbuildinfo
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "",
3
+ "version": "0.0.0",
4
+ "description": "",
5
+ "author": "",
6
+ "license": "",
7
+ "keywords": [
8
+ "typescript"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": ""
13
+ },
14
+ "type": "module",
15
+ "sideEffects": false,
16
+ "exports": {
17
+ ".": {
18
+ "entrypoint": "./src/index.ts",
19
+ "types": "./dist/index.d.ts",
20
+ "browser": "./dist/index.bundle.js",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "imports": {
25
+ "#src/*.js": "./src/*.ts"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "CHANGELOG.md",
30
+ "buildinfo.txt"
31
+ ],
32
+ "scripts": {
33
+ "clean:dist": "rm -rf dist/",
34
+ "clean:tsbuildinfo": "rm -f tsconfig.build.tsbuildinfo",
35
+ "clean": "bun run clean:dist && bun run clean:tsbuildinfo",
36
+ "prebuild": "bun run scripts/buildinfo.ts",
37
+ "tests": "bun test",
38
+ "build": "bun run build:lib && bun run build:lib-bundle",
39
+ "build:lib": "tsc --project tsconfig.build.json",
40
+ "build:lib-bundle": "bun run scripts/build-lib-bundle.ts",
41
+ "typecheck": "tsc --noEmit",
42
+ "dev": "bun run --watch src/dev.ts"
43
+ },
44
+ "devDependencies": {
45
+ "@types/bun": "latest",
46
+ "typescript": "^5.9.3"
47
+ }
48
+ }
@@ -0,0 +1,156 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import CDN_REWRITE_MAP from './cdn-rewrite-map.json';
4
+
5
+
6
+ interface ExportConditions {
7
+ [key: string]: string | undefined;
8
+ import?: string;
9
+ types?: string;
10
+ browser?: string;
11
+ entrypoint?: string;
12
+ }
13
+
14
+ interface DependencyMap {
15
+ [packageName: string]: string;
16
+ }
17
+
18
+ interface PackageManifest {
19
+ version: string;
20
+ exports?: Record<string, ExportConditions>;
21
+ dependencies?: DependencyMap;
22
+ devDependencies?: DependencyMap;
23
+ peerDependencies?: DependencyMap;
24
+ }
25
+
26
+ function getManifest(packageIdentifier?: string): PackageManifest {
27
+
28
+ const manifestPath = packageIdentifier
29
+ ? join('node_modules', packageIdentifier, 'package.json')
30
+ : 'package.json';
31
+
32
+ let manifest: PackageManifest;
33
+ try {
34
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
35
+ } catch {
36
+ if (packageIdentifier)
37
+ throw new Error("[scripts/build-lib-bundle.ts] Could not read manifest for '" + packageIdentifier + "' at '" + manifestPath + "'. Is it installed?");
38
+ else
39
+ throw new Error("[scripts/build-lib-bundle.ts] Could not read package manifest at '" + manifestPath + "'.");
40
+ }
41
+
42
+ return manifest;
43
+
44
+ }
45
+
46
+ function getManifestEntrypoints(packageManifest: PackageManifest): string[] {
47
+
48
+ const exports = packageManifest.exports;
49
+ if (!exports) throw new Error('[scripts/build-lib-bundle.ts] No exports field found in package.json.');
50
+
51
+ const entrypoints = Object.entries(exports)
52
+ .map(([key, conditions]) => {
53
+ if (!conditions.entrypoint) throw new Error(`[scripts/build-lib-bundle.ts] Export '${key}' does not have an 'entrypoint' condition.`);
54
+ return conditions.entrypoint;
55
+ });
56
+
57
+ return entrypoints;
58
+
59
+ }
60
+
61
+ function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string): string {
62
+
63
+ let version: string | undefined;
64
+ if (packageIdentifier) {
65
+
66
+ const dependencies = {
67
+ ...manifest.dependencies,
68
+ ...manifest.devDependencies,
69
+ ...manifest.peerDependencies,
70
+ };
71
+
72
+ version = dependencies[packageIdentifier];
73
+ if (!version) throw new Error(`[scripts/build-lib-bundle.ts] Package '${packageIdentifier}' is not listed in dependencies.`);
74
+
75
+ }
76
+ else {
77
+ if (!manifest.version) throw new Error('[scripts/build-lib-bundle.ts] Package manifest does not contain a version field.');
78
+ version = manifest.version;
79
+ }
80
+
81
+ return version;
82
+
83
+ }
84
+
85
+ function resolveCdnUrl(importSpecifier: string, urlTemplate: string): string {
86
+ const manifest = getManifest();
87
+ const version = getPackageVersion(manifest, importSpecifier);
88
+ return urlTemplate.replace('<VERSION>', version);
89
+ }
90
+
91
+ const cdnRewritePlugin = {
92
+ name: 'cdn-rewrite',
93
+ setup(build: any) {
94
+
95
+ const resolved = new Map<string, string>();
96
+ for (const [importSpecifier, urlTemplate] of Object.entries(CDN_REWRITE_MAP) as [string, string][]) {
97
+ const url = resolveCdnUrl(importSpecifier, urlTemplate);
98
+ resolved.set(importSpecifier, url);
99
+ console.log(`[cdn-rewrite] '${importSpecifier}' → '${url}'`);
100
+ }
101
+
102
+ build.onResolve({ filter: /.*/ }, (args: any) => {
103
+ const url = resolved.get(args.path);
104
+ if (url) return { path: url, external: true };
105
+ });
106
+
107
+ },
108
+ };
109
+
110
+
111
+ const entrypoints = getManifestEntrypoints(getManifest());
112
+ console.log('[scripts/build-lib-bundle.ts] Entrypoints:', entrypoints);
113
+
114
+ let buildResult;
115
+
116
+ console.log('[scripts/build-lib-bundle.ts] Starting ESM bundle build...');
117
+ buildResult = await Bun.build({
118
+ entrypoints,
119
+ outdir: 'dist',
120
+ naming: '[dir]/[name].bundle.[ext]',
121
+ target: 'browser',
122
+ format: 'esm',
123
+ minify: true,
124
+ sourcemap: 'external',
125
+ plugins: [cdnRewritePlugin],
126
+ });
127
+
128
+ if (!buildResult.success) {
129
+ console.error('[scripts/build-lib-bundle.ts] Build failed:');
130
+ for (const message of buildResult.logs) {
131
+ console.error(message);
132
+ }
133
+ process.exit(1);
134
+ }
135
+ console.log('[scripts/build-lib-bundle.ts] ESM bundle build completed successfully.');
136
+
137
+ console.log('[scripts/build-lib-bundle.ts] Starting IIFE bundle build...');
138
+ buildResult = await Bun.build({
139
+ entrypoints,
140
+ outdir: 'dist',
141
+ naming: '[dir]/[name].iife.[ext]',
142
+ target: 'browser',
143
+ format: 'iife',
144
+ minify: true,
145
+ sourcemap: 'external',
146
+ plugins: [cdnRewritePlugin],
147
+ });
148
+
149
+ if (!buildResult.success) {
150
+ console.error('[scripts/build-lib-bundle.ts] Build failed:');
151
+ for (const message of buildResult.logs) {
152
+ console.error(message);
153
+ }
154
+ process.exit(1);
155
+ }
156
+ console.log('[scripts/build-lib-bundle.ts] IIFE bundle build completed successfully.');
@@ -0,0 +1,21 @@
1
+ import { execSync } from 'child_process';
2
+ import { writeFileSync } from 'fs';
3
+
4
+
5
+ const BUILD_INFO_FILE = 'buildinfo.txt';
6
+ const GIT_COMMAND = 'git rev-parse --short HEAD';
7
+
8
+ const version = process.env.npm_package_version ?? '0.0.0';
9
+
10
+ let gitHash = '';
11
+ try {
12
+ gitHash = execSync(GIT_COMMAND, { stdio: ['ignore', 'pipe', 'ignore'] })
13
+ .toString()
14
+ .trim();
15
+ } catch { }
16
+
17
+ const buildinfo = gitHash ? `${version}+${gitHash}` : version;
18
+
19
+ writeFileSync(BUILD_INFO_FILE, buildinfo);
20
+
21
+ console.log(`'${BUILD_INFO_FILE}' has been updated with build info: ${buildinfo}`);
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,15 @@
1
+ import { buildinfoUrl, changelogUrl, distUrl } from '#src/urls.js';
2
+ import { fileURLToPath } from 'url';
3
+
4
+
5
+ const start: number = performance.now();
6
+
7
+ console.log(`'distPath':`, fileURLToPath(distUrl));
8
+ console.log(`'buildinfoPath':`, fileURLToPath(buildinfoUrl));
9
+ console.log(`'changelogPath':`, fileURLToPath(changelogUrl));
10
+
11
+
12
+ // dev code
13
+
14
+
15
+ console.log(`${(performance.now() - start).toFixed(3)}ms`);
File without changes
@@ -0,0 +1,3 @@
1
+ export const distUrl: URL = new URL('.', import.meta.url);
2
+ export const changelogUrl: URL = new URL('../CHANGELOG.md', distUrl);
3
+ export const buildinfoUrl: URL = new URL('../buildinfo.txt', distUrl);
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { readFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { buildinfoUrl } from '#src/urls.js';
5
+
6
+
7
+ describe('buildInfo', () => {
8
+
9
+ it('should follow semantic versioning format with optional commit hash', () => {
10
+ const buildinfoPath = fileURLToPath(buildinfoUrl);
11
+ const buildinfo = readFileSync(buildinfoPath, 'utf-8').trim();
12
+ expect(buildinfo).toMatch(/^\d+\.\d+\.\d+(\+[0-9a-f]+)?$/);
13
+ });
14
+
15
+ it('should not contain whitespace', () => {
16
+ const buildinfoPath = fileURLToPath(buildinfoUrl);
17
+ const buildinfo = readFileSync(buildinfoPath, 'utf-8').trim();
18
+ expect(buildinfo).not.toMatch(/\s/);
19
+ });
20
+
21
+ it('should not be empty or dash', () => {
22
+ const buildinfoPath = fileURLToPath(buildinfoUrl);
23
+ const buildinfo = readFileSync(buildinfoPath, 'utf-8').trim();
24
+ expect(buildinfo.length).toBeGreaterThan(0);
25
+ expect(buildinfo).not.toBe('-');
26
+ });
27
+
28
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext",
7
+ "emitDeclarationOnly": false,
8
+ },
9
+ "include": [
10
+ "src/**/*.ts"
11
+ ],
12
+ "exclude": [
13
+ "src/dev.ts",
14
+ "node_modules",
15
+ "dist",
16
+ "tests",
17
+ "scripts"
18
+ ]
19
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": [
6
+ "ES2022",
7
+ "DOM"
8
+ ],
9
+ "moduleResolution": "bundler",
10
+ "strict": true,
11
+ "verbatimModuleSyntax": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "noImplicitOverride": true,
15
+ "isolatedDeclarations": true,
16
+ "esModuleInterop": true,
17
+ "composite": true,
18
+ "skipLibCheck": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "resolveJsonModule": true,
21
+ "declaration": true,
22
+ "declarationMap": true,
23
+ "emitDeclarationOnly": true,
24
+ "outDir": "./dist",
25
+ "rootDir": ".",
26
+ },
27
+ "include": [
28
+ "src/**/*.ts",
29
+ "tests/**/*.ts",
30
+ "scripts/**/*.ts",
31
+ "scripts/**/*.json"
32
+ ],
33
+ "exclude": [
34
+ "node_modules",
35
+ "dist"
36
+ ]
37
+ }