monecromanci 0.0.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.
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Generates `dist/package.json` for a publishable library or CLI tool.
5
+ *
6
+ * Because every dependency lives in the monorepo ROOT package.json, a project's
7
+ * own package.json declares no runtime deps. This script fixes the classic
8
+ * "published a package with no dependencies" problem: it scans the built output
9
+ * for the packages actually imported, resolves their versions from the root
10
+ * manifest (and internal workspace packages from their own package.json), and
11
+ * writes a correct, publishable `dist/package.json`.
12
+ *
13
+ * Run from a project directory (cwd = project root) after the build emits dist:
14
+ * tsc -p ./tsconfig.lib.json && node ../../tools/generate-dist-package.mjs
15
+ *
16
+ * Dist field overrides (main/types/bin) come from `monecromanci.dist` in the
17
+ * project package.json.
18
+ */
19
+ import { builtinModules } from 'node:module'
20
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
21
+ import { join, resolve } from 'node:path'
22
+ import process from 'node:process'
23
+
24
+ const BUILTINS = new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)])
25
+
26
+ const projectRoot = process.cwd()
27
+ const distDir = join(projectRoot, 'dist')
28
+
29
+ if (!existsSync(distDir)) {
30
+ throw new Error(`dist folder not found at ${distDir} — build the project first`)
31
+ }
32
+
33
+ /** Reads and parses a JSON file, returning {} on failure. */
34
+ function readJson (filePath) {
35
+ try {
36
+ return JSON.parse(readFileSync(filePath, 'utf8'))
37
+ } catch {
38
+ return {}
39
+ }
40
+ }
41
+
42
+ /** Finds the workspace root by walking up to a package.json with `workspaces`. */
43
+ function findWorkspaceRoot (start) {
44
+ let directory = start
45
+ for (;;) {
46
+ const manifest = readJson(join(directory, 'package.json'))
47
+ if (manifest.workspaces) {
48
+ return directory
49
+ }
50
+
51
+ const parent = resolve(directory, '..')
52
+ if (parent === directory) {
53
+ return start
54
+ }
55
+ directory = parent
56
+ }
57
+ }
58
+
59
+ /** Resolves a package name from an import specifier (handles scopes). */
60
+ function packageNameOf (specifier) {
61
+ if (specifier.startsWith('@')) {
62
+ const [scope, name] = specifier.split('/')
63
+ return `${scope}/${name ?? ''}`
64
+ }
65
+
66
+ return specifier.split('/', 1)[0]
67
+ }
68
+
69
+ /** Returns whether a specifier is a relative or absolute path import. */
70
+ function isPathSpecifier (specifier) {
71
+ return (
72
+ specifier.startsWith('.') ||
73
+ specifier.startsWith('/') ||
74
+ /^[a-zA-Z]:[/\\]/.test(specifier)
75
+ )
76
+ }
77
+
78
+ /** Lists every emitted JS file under a directory. */
79
+ function listJsFiles (directory) {
80
+ const files = []
81
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
82
+ const full = join(directory, entry.name)
83
+ if (entry.isDirectory()) {
84
+ files.push(...listJsFiles(full))
85
+ } else if (/\.(c|m)?js$/i.test(entry.name)) {
86
+ files.push(full)
87
+ }
88
+ }
89
+
90
+ return files
91
+ }
92
+
93
+ /** Extracts import/require/export specifiers from JS source. */
94
+ function extractSpecifiers (source) {
95
+ const found = new Set()
96
+ const patterns = [
97
+ /\bimport\s+[^'"\n]+\s+from\s+['"]([^'"]+)['"]/g,
98
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
99
+ /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
100
+ /\bexport\s+[^'"\n]+\s+from\s+['"]([^'"]+)['"]/g,
101
+ ]
102
+ for (const pattern of patterns) {
103
+ for (const match of source.matchAll(pattern)) {
104
+ if (match[1]) {
105
+ found.add(match[1])
106
+ }
107
+ }
108
+ }
109
+
110
+ return [...found]
111
+ }
112
+
113
+ /** Maps workspace package names to their declared versions. */
114
+ function resolveWorkspaceVersions (workspaceRoot, rootManifest) {
115
+ const map = new Map()
116
+ for (const pattern of rootManifest.workspaces ?? []) {
117
+ if (!pattern.endsWith('/*')) {
118
+ continue
119
+ }
120
+
121
+ const parent = join(workspaceRoot, pattern.slice(0, -2))
122
+ if (!existsSync(parent)) {
123
+ continue
124
+ }
125
+
126
+ for (const child of readdirSync(parent, { withFileTypes: true })) {
127
+ if (!child.isDirectory()) {
128
+ continue
129
+ }
130
+
131
+ const manifest = readJson(join(parent, child.name, 'package.json'))
132
+ if (manifest.name && manifest.version) {
133
+ map.set(manifest.name, manifest.version)
134
+ }
135
+ }
136
+ }
137
+
138
+ return map
139
+ }
140
+
141
+ const sourceManifest = readJson(join(projectRoot, 'package.json'))
142
+ const workspaceRoot = findWorkspaceRoot(projectRoot)
143
+ const rootManifest = readJson(join(workspaceRoot, 'package.json'))
144
+ const rootDependencies = {
145
+ ...rootManifest.dependencies,
146
+ ...rootManifest.optionalDependencies,
147
+ ...rootManifest.devDependencies,
148
+ }
149
+ const workspaceVersions = resolveWorkspaceVersions(workspaceRoot, rootManifest)
150
+
151
+ const used = new Set()
152
+ for (const file of listJsFiles(distDir)) {
153
+ for (const specifier of extractSpecifiers(readFileSync(file, 'utf8'))) {
154
+ if (isPathSpecifier(specifier) || BUILTINS.has(specifier)) {
155
+ continue
156
+ }
157
+ used.add(packageNameOf(specifier))
158
+ }
159
+ }
160
+
161
+ const dependencies = {}
162
+ const missing = []
163
+ for (const name of [...used].sort((a, b) => a.localeCompare(b))) {
164
+ if (workspaceVersions.has(name)) {
165
+ dependencies[name] = `^${workspaceVersions.get(name)}`
166
+ } else if (rootDependencies[name]) {
167
+ dependencies[name] = rootDependencies[name]
168
+ } else {
169
+ missing.push(name)
170
+ }
171
+ }
172
+
173
+ if (missing.length > 0) {
174
+ throw new Error(`Missing version for: ${missing.join(', ')} — add them to the root package.json`)
175
+ }
176
+
177
+ const dist = sourceManifest.monecromanci?.dist ?? {}
178
+ const distManifest = {
179
+ name: sourceManifest.name,
180
+ version: sourceManifest.version,
181
+ description: sourceManifest.description,
182
+ license: sourceManifest.license,
183
+ main: dist.main ?? './index.js',
184
+ types: dist.types ?? './index.d.ts',
185
+ ...(dist.bin ? { bin: dist.bin } : {}),
186
+ ...(sourceManifest.repository ? { repository: sourceManifest.repository } : {}),
187
+ ...(sourceManifest.publishConfig ? { publishConfig: sourceManifest.publishConfig } : {}),
188
+ dependencies,
189
+ }
190
+
191
+ writeFileSync(join(distDir, 'package.json'), `${JSON.stringify(distManifest, undefined, 2)}\n`, 'utf8')
192
+
193
+ // CLI bins must be executable scripts: prepend a shebang if the bundler omitted it.
194
+ const shebang = '#!/usr/bin/env node\n'
195
+ for (const binPath of Object.values(dist.bin ?? {})) {
196
+ const binFile = join(distDir, binPath)
197
+ if (existsSync(binFile)) {
198
+ const content = readFileSync(binFile, 'utf8')
199
+ if (!content.startsWith('#!')) {
200
+ writeFileSync(binFile, shebang + content, 'utf8')
201
+ }
202
+ }
203
+ }
204
+
205
+ process.stdout.write(`Wrote dist/package.json for ${distManifest.name} (${Object.keys(dependencies).length} deps)\n`)
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Builds a Next.js app for one environment and assembles a self-contained drop
5
+ * in `dist-<env>`. The output mode is chosen by NEXT_OUTPUT:
6
+ * - 'standalone' (default): a runnable Node server (`node dist-<env>/server.js`)
7
+ * - 'export': a static site
8
+ *
9
+ * Run per environment with the env file loaded, e.g.:
10
+ * dotenv -e .env.dev -- node ../../tools/next-build.mjs dev
11
+ */
12
+ import { execSync } from 'node:child_process'
13
+ import { cpSync, existsSync, rmSync } from 'node:fs'
14
+ import { join } from 'node:path'
15
+ import process from 'node:process'
16
+
17
+ const environment = process.argv[2] ?? 'dev'
18
+ const mode = process.env.NEXT_OUTPUT === 'export' ? 'export' : 'standalone'
19
+ const cwd = process.cwd()
20
+ const distDir = join(cwd, `dist-${environment}`)
21
+
22
+ rmSync(distDir, { recursive: true, force: true })
23
+ execSync('next build', { stdio: 'inherit', cwd, env: { ...process.env, NEXT_OUTPUT: mode } })
24
+
25
+ if (mode === 'export') {
26
+ cpSync(join(cwd, 'out'), distDir, { recursive: true })
27
+ } else {
28
+ cpSync(join(cwd, '.next', 'standalone'), distDir, { recursive: true })
29
+ if (existsSync(join(cwd, '.next', 'static'))) {
30
+ cpSync(join(cwd, '.next', 'static'), join(distDir, '.next', 'static'), { recursive: true })
31
+ }
32
+ if (existsSync(join(cwd, 'public'))) {
33
+ cpSync(join(cwd, 'public'), join(distDir, 'public'), { recursive: true })
34
+ }
35
+ }
36
+
37
+ process.stdout.write(`Assembled dist-${environment} (mode: ${mode})\n`)