@a35hie/ts-pkg 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/src/main.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type {
2
+ PackageConfig,
3
+ StandardPackageJson,
4
+ ScriptPreset,
5
+ DependencyInput,
6
+ ConditionalConfig,
7
+ License,
8
+ } from './schemas/package'
9
+ import {
10
+ createPackageJson,
11
+ writePackageJson,
12
+ type GenerateOptions,
13
+ } from './generator/createPackageJson'
14
+
15
+ export function definePackageConfig(config: PackageConfig): PackageConfig {
16
+ return config
17
+ }
18
+
19
+ // Run CLI if executed directly
20
+ async function main() {
21
+ const configPath = process.argv[2] ?? 'package.config.ts'
22
+ const outputPath = process.argv[3] ?? 'package.json'
23
+
24
+ try {
25
+ const configModule = await import(Bun.pathToFileURL(configPath).href)
26
+ const config: PackageConfig = configModule.default ?? configModule
27
+
28
+ await writePackageJson(config, { outputPath })
29
+ } catch (error) {
30
+ if ((error as NodeJS.ErrnoException).code === 'ERR_MODULE_NOT_FOUND') {
31
+ console.error(`❌ Config file not found: ${configPath}`)
32
+ console.error('\nCreate a package.config.ts file with:')
33
+ console.error(`
34
+ import { definePackageConfig } from './src/main'
35
+
36
+ export default definePackageConfig({
37
+ name: 'my-package',
38
+ version: '1.0.0',
39
+ scriptPresets: ['typescript', 'testing'],
40
+ dependencies: ['lodash', 'zod'],
41
+ devDependencies: ['typescript', 'vitest'],
42
+ })
43
+ `)
44
+ process.exit(1)
45
+ }
46
+ throw error
47
+ }
48
+ }
49
+
50
+ // Export everything
51
+ export {
52
+ createPackageJson,
53
+ writePackageJson,
54
+ type PackageConfig,
55
+ type StandardPackageJson,
56
+ type ScriptPreset,
57
+ type DependencyInput,
58
+ type ConditionalConfig,
59
+ type GenerateOptions,
60
+ type License,
61
+ }
62
+
63
+ // Run if main module
64
+ if (import.meta.main) {
65
+ main()
66
+ }
@@ -0,0 +1,59 @@
1
+ import type { ScriptPreset } from '../schemas/package'
2
+
3
+ type ScriptDefinitions = Record<string, string>
4
+
5
+ const scriptPresets: Record<ScriptPreset, ScriptDefinitions> = {
6
+ typescript: {
7
+ build: 'tsc',
8
+ 'build:watch': 'tsc --watch',
9
+ typecheck: 'tsc --noEmit',
10
+ },
11
+
12
+ react: {
13
+ dev: 'vite',
14
+ build: 'vite build',
15
+ preview: 'vite preview',
16
+ },
17
+
18
+ node: {
19
+ start: 'node dist/index.js',
20
+ dev: 'tsx watch src/index.ts',
21
+ build: 'tsup src/index.ts --format esm,cjs --dts',
22
+ },
23
+
24
+ testing: {
25
+ test: 'vitest',
26
+ 'test:watch': 'vitest watch',
27
+ 'test:coverage': 'vitest --coverage',
28
+ },
29
+
30
+ prettier: {
31
+ format: 'prettier --write .',
32
+ 'format:check': 'prettier --check .',
33
+ },
34
+
35
+ eslint: {
36
+ lint: 'eslint .',
37
+ 'lint:fix': 'eslint . --fix',
38
+ },
39
+ }
40
+
41
+ export function getPresetScripts(presets: ScriptPreset[]): ScriptDefinitions {
42
+ const merged: ScriptDefinitions = {}
43
+
44
+ for (const preset of presets) {
45
+ const scripts = scriptPresets[preset]
46
+ if (scripts) {
47
+ Object.assign(merged, scripts)
48
+ }
49
+ }
50
+
51
+ return merged
52
+ }
53
+
54
+ export function mergeScripts(
55
+ presetScripts: ScriptDefinitions,
56
+ customScripts?: ScriptDefinitions
57
+ ): ScriptDefinitions {
58
+ return { ...presetScripts, ...customScripts }
59
+ }
@@ -0,0 +1,107 @@
1
+ import type { DependencyInput } from '../schemas/package'
2
+
3
+ interface NpmPackageInfo {
4
+ 'dist-tags': {
5
+ latest: string
6
+ [tag: string]: string
7
+ }
8
+ versions: Record<string, unknown>
9
+ }
10
+
11
+ // Parse dependency input: 'lodash' | 'lodash@^4' | { lodash: '^4.0.0' }
12
+ function parseDependency(dep: DependencyInput): {
13
+ name: string
14
+ version?: string
15
+ } {
16
+ if (typeof dep === 'string') {
17
+ const atIndex = dep.lastIndexOf('@')
18
+ if (atIndex > 0) {
19
+ return {
20
+ name: dep.slice(0, atIndex),
21
+ version: dep.slice(atIndex + 1),
22
+ }
23
+ }
24
+ return { name: dep }
25
+ }
26
+
27
+ // Object form: { lodash: '^4.0.0' }
28
+ const [name, version] = Object.entries(dep)[0]!
29
+ return { name, version }
30
+ }
31
+
32
+ // Fetch latest version from npm registry
33
+ async function fetchLatestVersion(packageName: string): Promise<string> {
34
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`
35
+
36
+ const response = await fetch(url, {
37
+ headers: { Accept: 'application/json' },
38
+ })
39
+
40
+ if (!response.ok) {
41
+ throw new Error(`Failed to fetch ${packageName}: ${response.statusText}`)
42
+ }
43
+
44
+ const data = (await response.json()) as NpmPackageInfo
45
+ return data['dist-tags'].latest
46
+ }
47
+
48
+ // Resolve a single dependency to name: version pair
49
+ async function resolveDependency(
50
+ dep: DependencyInput
51
+ ): Promise<[string, string]> {
52
+ const { name, version } = parseDependency(dep)
53
+
54
+ if (version) {
55
+ return [name, version]
56
+ }
57
+
58
+ // Auto-resolve latest version with ^ prefix
59
+ const latestVersion = await fetchLatestVersion(name)
60
+ return [name, `^${latestVersion}`]
61
+ }
62
+
63
+ // Resolve all dependencies in parallel with batching
64
+ export async function resolveDependencies(
65
+ deps: DependencyInput[] | undefined
66
+ ): Promise<Record<string, string>> {
67
+ if (!deps || deps.length === 0) {
68
+ return {}
69
+ }
70
+
71
+ const results = await Promise.all(deps.map(resolveDependency))
72
+
73
+ return Object.fromEntries(results)
74
+ }
75
+
76
+ // Cache for resolved versions (persists during single run)
77
+ const versionCache = new Map<string, string>()
78
+
79
+ export async function resolveDependenciesCached(
80
+ deps: DependencyInput[] | undefined
81
+ ): Promise<Record<string, string>> {
82
+ if (!deps || deps.length === 0) {
83
+ return {}
84
+ }
85
+
86
+ const results: [string, string][] = []
87
+
88
+ for (const dep of deps) {
89
+ const { name, version } = parseDependency(dep)
90
+
91
+ if (version) {
92
+ results.push([name, version])
93
+ continue
94
+ }
95
+
96
+ // Check cache first
97
+ let resolvedVersion = versionCache.get(name)
98
+ if (!resolvedVersion) {
99
+ resolvedVersion = `^${await fetchLatestVersion(name)}`
100
+ versionCache.set(name, resolvedVersion)
101
+ }
102
+
103
+ results.push([name, resolvedVersion])
104
+ }
105
+
106
+ return Object.fromEntries(results)
107
+ }
@@ -0,0 +1,122 @@
1
+ // Common SPDX license identifiers
2
+ export type License =
3
+ | 'MIT'
4
+ | 'Apache-2.0'
5
+ | 'GPL-2.0-only'
6
+ | 'GPL-2.0-or-later'
7
+ | 'GPL-3.0-only'
8
+ | 'GPL-3.0-or-later'
9
+ | 'LGPL-2.1-only'
10
+ | 'LGPL-2.1-or-later'
11
+ | 'LGPL-3.0-only'
12
+ | 'LGPL-3.0-or-later'
13
+ | 'BSD-2-Clause'
14
+ | 'BSD-3-Clause'
15
+ | 'ISC'
16
+ | 'MPL-2.0'
17
+ | 'AGPL-3.0-only'
18
+ | 'AGPL-3.0-or-later'
19
+ | 'Unlicense'
20
+ | 'WTFPL'
21
+ | 'CC0-1.0'
22
+ | 'CC-BY-4.0'
23
+ | 'CC-BY-SA-4.0'
24
+ | 'Zlib'
25
+ | 'BSL-1.0'
26
+ | 'EPL-2.0'
27
+ | 'EUPL-1.2'
28
+ | 'CDDL-1.0'
29
+ | 'Artistic-2.0'
30
+ | 'OSL-3.0'
31
+ | 'AFL-3.0'
32
+ | 'LPPL-1.3c'
33
+ | (string & {}) // Allow custom licenses while preserving autocomplete
34
+
35
+ // Standard package.json fields
36
+ export interface StandardPackageJson {
37
+ name: string
38
+ version?: string
39
+ description?: string
40
+ keywords?: string[]
41
+ homepage?: string
42
+ bugs?: string | { url?: string; email?: string }
43
+ license?: License
44
+ author?: string | { name: string; email?: string; url?: string }
45
+ contributors?: (string | { name: string; email?: string; url?: string })[]
46
+ repository?: string | { type: string; url: string; directory?: string }
47
+ main?: string
48
+ module?: string
49
+ types?: string
50
+ exports?: Record<
51
+ string,
52
+ string | { import?: string; require?: string; types?: string }
53
+ >
54
+ bin?: string | Record<string, string>
55
+ files?: string[]
56
+ scripts?: Record<string, string>
57
+ dependencies?: Record<string, string>
58
+ devDependencies?: Record<string, string>
59
+ peerDependencies?: Record<string, string>
60
+ optionalDependencies?: Record<string, string>
61
+ engines?: Record<string, string>
62
+ os?: string[]
63
+ cpu?: string[]
64
+ private?: boolean
65
+ publishConfig?: Record<string, unknown>
66
+ workspaces?: string[]
67
+ type?: 'module' | 'commonjs'
68
+ }
69
+
70
+ // Script preset names
71
+ export type ScriptPreset =
72
+ | 'typescript'
73
+ | 'react'
74
+ | 'node'
75
+ | 'testing'
76
+ | 'prettier'
77
+ | 'eslint'
78
+
79
+ // Dependency can be: 'lodash' | 'lodash@^4' | { lodash: '^4.0.0' }
80
+ export type DependencyInput = string | Record<string, string>
81
+
82
+ // Conditional config block
83
+ export interface ConditionalConfig {
84
+ when: {
85
+ env?: string
86
+ platform?: NodeJS.Platform
87
+ nodeVersion?: string
88
+ ci?: boolean
89
+ }
90
+ set: Partial<StandardPackageJson>
91
+ }
92
+
93
+ // Magical package.json config with extra features
94
+ export interface PackageConfig extends Omit<
95
+ StandardPackageJson,
96
+ 'scripts' | 'dependencies' | 'devDependencies' | 'peerDependencies'
97
+ > {
98
+ // Extends another config
99
+ extends?: string | PackageConfig
100
+
101
+ // Script presets: auto-generate common scripts
102
+ scriptPresets?: ScriptPreset[]
103
+ scripts?: Record<string, string>
104
+
105
+ // Magical dependency inputs (auto-resolve versions)
106
+ dependencies?: DependencyInput[]
107
+ devDependencies?: DependencyInput[]
108
+ peerDependencies?: DependencyInput[]
109
+
110
+ // Conditional configuration
111
+ conditions?: ConditionalConfig[]
112
+
113
+ // Auto-infer fields
114
+ autoInfer?: {
115
+ version?: boolean // from git tags
116
+ repository?: boolean // from git remote
117
+ author?: boolean // from git config
118
+ }
119
+ }
120
+
121
+ // Re-export for backwards compatibility
122
+ export type PackageJson = PackageConfig
@@ -0,0 +1,67 @@
1
+ import type { ConditionalConfig, StandardPackageJson } from '../schemas/package'
2
+ import { deepMerge } from './merge'
3
+
4
+ interface ConditionContext {
5
+ env: string
6
+ platform: NodeJS.Platform
7
+ nodeVersion: string
8
+ ci: boolean
9
+ }
10
+
11
+ function getContext(): ConditionContext {
12
+ return {
13
+ env: process.env.NODE_ENV ?? 'development',
14
+ platform: process.platform,
15
+ nodeVersion: process.version,
16
+ ci: process.env.CI === 'true' || process.env.CI === '1',
17
+ }
18
+ }
19
+
20
+ function evaluateCondition(
21
+ when: ConditionalConfig['when'],
22
+ context: ConditionContext
23
+ ): boolean {
24
+ if (when.env !== undefined && context.env !== when.env) {
25
+ return false
26
+ }
27
+
28
+ if (when.platform !== undefined && context.platform !== when.platform) {
29
+ return false
30
+ }
31
+
32
+ if (when.ci !== undefined && context.ci !== when.ci) {
33
+ return false
34
+ }
35
+
36
+ if (when.nodeVersion !== undefined) {
37
+ // Simple semver check (starts with)
38
+ if (!context.nodeVersion.startsWith(when.nodeVersion)) {
39
+ return false
40
+ }
41
+ }
42
+
43
+ return true
44
+ }
45
+
46
+ export function applyConditions(
47
+ baseConfig: Partial<StandardPackageJson>,
48
+ conditions: ConditionalConfig[] | undefined
49
+ ): Partial<StandardPackageJson> {
50
+ if (!conditions || conditions.length === 0) {
51
+ return baseConfig
52
+ }
53
+
54
+ const context = getContext()
55
+ let result = { ...baseConfig }
56
+
57
+ for (const condition of conditions) {
58
+ if (evaluateCondition(condition.when, context)) {
59
+ result = deepMerge(
60
+ result,
61
+ condition.set as Record<string, unknown>
62
+ ) as Partial<StandardPackageJson>
63
+ }
64
+ }
65
+
66
+ return result
67
+ }
@@ -0,0 +1,91 @@
1
+ import type { PackageConfig } from '../schemas/package'
2
+
3
+ // Deep merge two objects, with source overriding target
4
+ export function deepMerge<T extends Record<string, unknown>>(
5
+ target: T,
6
+ source: Partial<T>
7
+ ): T {
8
+ const result = { ...target }
9
+
10
+ for (const key of Object.keys(source) as (keyof T)[]) {
11
+ const sourceValue = source[key]
12
+ const targetValue = target[key]
13
+
14
+ if (sourceValue === undefined) continue
15
+
16
+ if (
17
+ typeof sourceValue === 'object' &&
18
+ sourceValue !== null &&
19
+ !Array.isArray(sourceValue) &&
20
+ typeof targetValue === 'object' &&
21
+ targetValue !== null &&
22
+ !Array.isArray(targetValue)
23
+ ) {
24
+ result[key] = deepMerge(
25
+ targetValue as Record<string, unknown>,
26
+ sourceValue as Record<string, unknown>
27
+ ) as T[keyof T]
28
+ } else {
29
+ result[key] = sourceValue as T[keyof T]
30
+ }
31
+ }
32
+
33
+ return result
34
+ }
35
+
36
+ // Merge arrays (for dependencies)
37
+ export function mergeArrays<T>(
38
+ target: T[] | undefined,
39
+ source: T[] | undefined
40
+ ): T[] {
41
+ return [...(target ?? []), ...(source ?? [])]
42
+ }
43
+
44
+ // Resolve extends chain
45
+ export async function resolveExtends(
46
+ config: PackageConfig
47
+ ): Promise<PackageConfig> {
48
+ if (!config.extends) {
49
+ return config
50
+ }
51
+
52
+ let baseConfig: PackageConfig
53
+
54
+ if (typeof config.extends === 'string') {
55
+ // Import from file path
56
+ const imported = await import(config.extends)
57
+ baseConfig = imported.default ?? imported
58
+ } else {
59
+ baseConfig = config.extends
60
+ }
61
+
62
+ // Recursively resolve base config's extends
63
+ baseConfig = await resolveExtends(baseConfig)
64
+
65
+ // Merge base into current (current overrides base)
66
+ const { extends: _, ...currentWithoutExtends } = config
67
+
68
+ return {
69
+ ...baseConfig,
70
+ ...currentWithoutExtends,
71
+ // Merge arrays for dependencies
72
+ dependencies: mergeArrays(
73
+ baseConfig.dependencies,
74
+ currentWithoutExtends.dependencies
75
+ ),
76
+ devDependencies: mergeArrays(
77
+ baseConfig.devDependencies,
78
+ currentWithoutExtends.devDependencies
79
+ ),
80
+ peerDependencies: mergeArrays(
81
+ baseConfig.peerDependencies,
82
+ currentWithoutExtends.peerDependencies
83
+ ),
84
+ // Merge objects for scripts
85
+ scripts: { ...baseConfig.scripts, ...currentWithoutExtends.scripts },
86
+ scriptPresets: [
87
+ ...(baseConfig.scriptPresets ?? []),
88
+ ...(currentWithoutExtends.scriptPresets ?? []),
89
+ ],
90
+ }
91
+ }
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="GENERAL_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false,
28
+ "paths": {
29
+ "@/*": ["./src/*"]
30
+ }
31
+ }
32
+ }