@bobfrankston/npmglobalize 1.0.199 → 1.0.201
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/README.md +84 -15
- package/cli.js +5 -0
- package/lib.js +5 -5
- package/package.json +3 -3
- package/lib/config.d.ts +0 -23
- package/lib/config.js +0 -163
- package/lib/deps.d.ts +0 -42
- package/lib/deps.js +0 -298
- package/lib/git.d.ts +0 -91
- package/lib/git.js +0 -748
- package/lib/ignore.d.ts +0 -24
- package/lib/ignore.js +0 -346
- package/lib/npm.d.ts +0 -33
- package/lib/npm.js +0 -306
- package/lib/types.d.ts +0 -135
- package/lib/types.js +0 -80
package/lib/deps.js
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npmglobalize dependency analysis, transformation, and restoration.
|
|
3
|
-
* Handles file: ref discovery, workspace graphs, topological sorting,
|
|
4
|
-
* and transforming file: deps to npm versions for publishing.
|
|
5
|
-
*/
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { spawnSafe, colors, DEP_KEYS } from './types.js';
|
|
9
|
-
import { readPackageJson, isFileRef, resolveFilePath } from './config.js';
|
|
10
|
-
import { checkVersionExists } from './npm.js';
|
|
11
|
-
/** Get all file: dependencies from package.json */
|
|
12
|
-
export function getFileRefs(pkg) {
|
|
13
|
-
const refs = new Map();
|
|
14
|
-
for (const key of DEP_KEYS) {
|
|
15
|
-
if (!pkg[key])
|
|
16
|
-
continue;
|
|
17
|
-
for (const [name, value] of Object.entries(pkg[key])) {
|
|
18
|
-
if (isFileRef(value)) {
|
|
19
|
-
refs.set(`${key}:${name}`, { key, name, value: value });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return refs;
|
|
24
|
-
}
|
|
25
|
-
// ─── Workspace helpers ───────────────────────────────────────────────
|
|
26
|
-
/** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
|
|
27
|
-
export function resolveWorkspacePackages(rootDir) {
|
|
28
|
-
const rootPkg = readPackageJson(rootDir);
|
|
29
|
-
const workspaces = rootPkg.workspaces;
|
|
30
|
-
if (!Array.isArray(workspaces))
|
|
31
|
-
return [];
|
|
32
|
-
const results = [];
|
|
33
|
-
for (const entry of workspaces) {
|
|
34
|
-
const pkgDir = path.resolve(rootDir, entry);
|
|
35
|
-
const pkgJsonPath = path.join(pkgDir, 'package.json');
|
|
36
|
-
if (!fs.existsSync(pkgJsonPath))
|
|
37
|
-
continue;
|
|
38
|
-
const pkg = readPackageJson(pkgDir);
|
|
39
|
-
if (pkg.private)
|
|
40
|
-
continue; // skip private packages (they can't be published)
|
|
41
|
-
results.push({ name: pkg.name || entry, dir: pkgDir, pkg });
|
|
42
|
-
}
|
|
43
|
-
return results;
|
|
44
|
-
}
|
|
45
|
-
/** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
|
|
46
|
-
export function buildDependencyGraph(packages) {
|
|
47
|
-
const nameSet = new Set(packages.map(p => p.name));
|
|
48
|
-
const dirToName = new Map();
|
|
49
|
-
for (const p of packages) {
|
|
50
|
-
dirToName.set(p.dir, p.name);
|
|
51
|
-
}
|
|
52
|
-
const graph = new Map();
|
|
53
|
-
for (const p of packages) {
|
|
54
|
-
const deps = new Set();
|
|
55
|
-
for (const depKey of DEP_KEYS) {
|
|
56
|
-
if (!p.pkg[depKey])
|
|
57
|
-
continue;
|
|
58
|
-
for (const [depName, depValue] of Object.entries(p.pkg[depKey])) {
|
|
59
|
-
// Check by npm name
|
|
60
|
-
if (nameSet.has(depName)) {
|
|
61
|
-
deps.add(depName);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
// Check file: refs that resolve to a sibling workspace dir
|
|
65
|
-
if (isFileRef(depValue)) {
|
|
66
|
-
const resolved = resolveFilePath(depValue, p.dir);
|
|
67
|
-
const resolvedNorm = path.resolve(resolved);
|
|
68
|
-
const match = dirToName.get(resolvedNorm);
|
|
69
|
-
if (match) {
|
|
70
|
-
deps.add(match);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
graph.set(p.name, deps);
|
|
76
|
-
}
|
|
77
|
-
return graph;
|
|
78
|
-
}
|
|
79
|
-
/** Topological sort with cycle detection. Returns package names in dependency order. */
|
|
80
|
-
export function topologicalSort(graph) {
|
|
81
|
-
const visited = new Set();
|
|
82
|
-
const visiting = new Set(); // cycle detection
|
|
83
|
-
const result = [];
|
|
84
|
-
function visit(node) {
|
|
85
|
-
if (visited.has(node))
|
|
86
|
-
return;
|
|
87
|
-
if (visiting.has(node)) {
|
|
88
|
-
throw new Error(`Circular dependency detected involving: ${node}`);
|
|
89
|
-
}
|
|
90
|
-
visiting.add(node);
|
|
91
|
-
const deps = graph.get(node);
|
|
92
|
-
if (deps) {
|
|
93
|
-
for (const dep of deps) {
|
|
94
|
-
if (dep === node)
|
|
95
|
-
continue; // skip self-references
|
|
96
|
-
if (graph.has(dep)) { // only visit workspace-internal deps
|
|
97
|
-
visit(dep);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
visiting.delete(node);
|
|
102
|
-
visited.add(node);
|
|
103
|
-
result.push(node);
|
|
104
|
-
}
|
|
105
|
-
for (const node of graph.keys()) {
|
|
106
|
-
visit(node);
|
|
107
|
-
}
|
|
108
|
-
return result;
|
|
109
|
-
}
|
|
110
|
-
/** Check if a published npm package has broken deps (file: paths or unpublished transitive deps).
|
|
111
|
-
* Also checks local file: deps for unpublished versions. */
|
|
112
|
-
function hasUnpublishedTransitiveDeps(packageName, pkg, baseDir, verbose) {
|
|
113
|
-
// Check 1: Does the npm-published version have file: paths in its deps?
|
|
114
|
-
try {
|
|
115
|
-
const result = spawnSafe('npm', ['view', packageName, 'dependencies', '--json'], {
|
|
116
|
-
encoding: 'utf-8',
|
|
117
|
-
stdio: 'pipe',
|
|
118
|
-
shell: true
|
|
119
|
-
});
|
|
120
|
-
if (result.status === 0 && result.stdout.trim()) {
|
|
121
|
-
const npmDeps = JSON.parse(result.stdout.trim());
|
|
122
|
-
for (const [depName, depValue] of Object.entries(npmDeps)) {
|
|
123
|
-
if (isFileRef(depValue)) {
|
|
124
|
-
if (verbose) {
|
|
125
|
-
console.log(colors.yellow(` npm copy has file: dep ${depName} → ${depValue}`));
|
|
126
|
-
}
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// Can't check npm — fall through to local check
|
|
134
|
-
}
|
|
135
|
-
// Check 2: Do local file: deps have unpublished versions?
|
|
136
|
-
for (const key of DEP_KEYS) {
|
|
137
|
-
if (!pkg[key])
|
|
138
|
-
continue;
|
|
139
|
-
for (const [depName, depValue] of Object.entries(pkg[key])) {
|
|
140
|
-
if (!isFileRef(depValue))
|
|
141
|
-
continue;
|
|
142
|
-
try {
|
|
143
|
-
const depPath = resolveFilePath(depValue, baseDir);
|
|
144
|
-
const depPkg = readPackageJson(depPath);
|
|
145
|
-
if (!checkVersionExists(depName, depPkg.version)) {
|
|
146
|
-
if (verbose) {
|
|
147
|
-
console.log(colors.yellow(` transitive dep ${depName}@${depPkg.version} not on npm`));
|
|
148
|
-
}
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
/** Transform file: dependencies to npm versions */
|
|
160
|
-
export function transformDeps(pkg, baseDir, verbose = false, forcePublish = false) {
|
|
161
|
-
let transformed = false;
|
|
162
|
-
const unpublished = [];
|
|
163
|
-
for (const key of DEP_KEYS) {
|
|
164
|
-
if (!pkg[key])
|
|
165
|
-
continue;
|
|
166
|
-
const dotKey = '.' + key;
|
|
167
|
-
// If .dependencies already exists, restore it and merge any new dependencies
|
|
168
|
-
if (pkg[dotKey]) {
|
|
169
|
-
// Save any new dependencies that aren't in .dependencies
|
|
170
|
-
const currentDeps = { ...pkg[key] };
|
|
171
|
-
// Restore .dependencies back to dependencies
|
|
172
|
-
pkg[key] = { ...pkg[dotKey] };
|
|
173
|
-
// Merge in any NEW dependencies that were added since transformation
|
|
174
|
-
for (const [name, value] of Object.entries(currentDeps)) {
|
|
175
|
-
if (!(name in pkg[dotKey])) {
|
|
176
|
-
// This is a new dependency, add it
|
|
177
|
-
pkg[key][name] = value;
|
|
178
|
-
// Also add to .dependencies backup
|
|
179
|
-
pkg[dotKey][name] = value;
|
|
180
|
-
if (verbose) {
|
|
181
|
-
console.log(` Merged new dependency: ${name}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
const hasFileRefs = Object.values(pkg[key]).some(v => isFileRef(v));
|
|
187
|
-
if (!hasFileRefs)
|
|
188
|
-
continue;
|
|
189
|
-
// Backup original (or update existing backup with merged deps)
|
|
190
|
-
if (!pkg[dotKey]) {
|
|
191
|
-
pkg[dotKey] = { ...pkg[key] };
|
|
192
|
-
}
|
|
193
|
-
// Transform file: refs to npm versions
|
|
194
|
-
for (const [name, value] of Object.entries(pkg[key])) {
|
|
195
|
-
if (isFileRef(value)) {
|
|
196
|
-
const targetPath = resolveFilePath(value, baseDir);
|
|
197
|
-
console.log(colors.blue(` + ${name} → ${value}`));
|
|
198
|
-
try {
|
|
199
|
-
const targetPkg = readPackageJson(targetPath);
|
|
200
|
-
const targetVersion = targetPkg.version;
|
|
201
|
-
const npmVersion = '^' + targetVersion;
|
|
202
|
-
// Check if this version exists on npm (or if force publish)
|
|
203
|
-
const versionExists = forcePublish ? false : checkVersionExists(name, targetVersion);
|
|
204
|
-
if (!versionExists) {
|
|
205
|
-
unpublished.push({ name, version: targetVersion, path: targetPath });
|
|
206
|
-
if (forcePublish) {
|
|
207
|
-
console.log(colors.yellow(` ⟳ ${name}@${targetVersion} will be republished (--force-publish)`));
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
console.log(colors.red(` ⚠ ${name}@${targetVersion} not found on npm (local: ${value})`));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
if (verbose) {
|
|
215
|
-
console.log(colors.green(` ✓ ${name}@${targetVersion} exists on npm`));
|
|
216
|
-
}
|
|
217
|
-
// Check transitive file: deps — if any are unpublished, this dep needs republishing
|
|
218
|
-
if (hasUnpublishedTransitiveDeps(name, targetPkg, targetPath, verbose)) {
|
|
219
|
-
unpublished.push({ name, version: targetVersion, path: targetPath });
|
|
220
|
-
console.log(colors.yellow(` ⟳ ${name}@${targetVersion} has unpublished transitive deps — will republish`));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
pkg[key][name] = npmVersion;
|
|
224
|
-
if (verbose) {
|
|
225
|
-
console.log(` ${name}: ${value} -> ${npmVersion}`);
|
|
226
|
-
}
|
|
227
|
-
transformed = true;
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
throw new Error(`Failed to resolve ${name} at ${targetPath}: ${error.message}`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return { transformed, unpublished };
|
|
236
|
-
}
|
|
237
|
-
/** Build and print a dependency tree of file: references.
|
|
238
|
-
* Recursively walks file: deps showing the full hierarchy with indentation. */
|
|
239
|
-
export function printDepTree(baseDir, indent = 0, visited = new Set()) {
|
|
240
|
-
const realDir = fs.realpathSync(baseDir);
|
|
241
|
-
if (visited.has(realDir))
|
|
242
|
-
return;
|
|
243
|
-
visited.add(realDir);
|
|
244
|
-
let pkg;
|
|
245
|
-
try {
|
|
246
|
-
pkg = readPackageJson(baseDir);
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
for (const key of DEP_KEYS) {
|
|
252
|
-
if (!pkg[key])
|
|
253
|
-
continue;
|
|
254
|
-
// Use the backup (.dependencies) if present — it has the original file: refs
|
|
255
|
-
const deps = pkg['.' + key] || pkg[key];
|
|
256
|
-
for (const [name, value] of Object.entries(deps)) {
|
|
257
|
-
if (!isFileRef(value))
|
|
258
|
-
continue;
|
|
259
|
-
const prefix = ' '.repeat(indent * 2);
|
|
260
|
-
let targetPath;
|
|
261
|
-
let version = '?';
|
|
262
|
-
try {
|
|
263
|
-
targetPath = resolveFilePath(value, baseDir);
|
|
264
|
-
const targetPkg = readPackageJson(targetPath);
|
|
265
|
-
version = targetPkg.version || '?';
|
|
266
|
-
}
|
|
267
|
-
catch {
|
|
268
|
-
console.log(`${prefix} ${name} → ${value} ${colors.red('(unresolvable)')}`);
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
const exists = checkVersionExists(name, version);
|
|
272
|
-
const marker = exists ? colors.green('✓') : colors.red('✗');
|
|
273
|
-
console.log(`${prefix} ${marker} ${name}@${version} → ${value}`);
|
|
274
|
-
printDepTree(targetPath, indent + 1, visited);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
/** Restore file: dependencies from .dependencies */
|
|
279
|
-
export function restoreDeps(pkg, verbose = false) {
|
|
280
|
-
let restored = false;
|
|
281
|
-
for (const key of DEP_KEYS) {
|
|
282
|
-
const dotKey = '.' + key;
|
|
283
|
-
if (pkg[dotKey]) {
|
|
284
|
-
pkg[key] = pkg[dotKey];
|
|
285
|
-
delete pkg[dotKey];
|
|
286
|
-
restored = true;
|
|
287
|
-
if (verbose) {
|
|
288
|
-
console.log(` Restored ${key} from ${dotKey}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return restored;
|
|
293
|
-
}
|
|
294
|
-
/** Check if .dependencies exist (already transformed) */
|
|
295
|
-
export function hasBackup(pkg) {
|
|
296
|
-
return DEP_KEYS.some(key => pkg['.' + key]);
|
|
297
|
-
}
|
|
298
|
-
//# sourceMappingURL=deps.js.map
|
package/lib/git.d.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npmglobalize — Git operations, command execution, and push protection.
|
|
3
|
-
* Depends on: types (leaf), config (leaf)
|
|
4
|
-
*/
|
|
5
|
-
import { type GitStatus } from './types.js';
|
|
6
|
-
/**
|
|
7
|
-
* Remove 'nul' files from a directory tree (Windows reserved name issue).
|
|
8
|
-
* These files break git and npm on Windows. Uses \\?\ prefix to bypass name validation.
|
|
9
|
-
*/
|
|
10
|
-
export declare function removeNulFiles(dir: string, visited?: Set<string>): number;
|
|
11
|
-
/** Repair a corrupted git index by rebuilding it.
|
|
12
|
-
* Handles "invalid object" / "Error building trees" errors caused by
|
|
13
|
-
* stale entries in .git/index referencing missing objects. */
|
|
14
|
-
export declare function repairGitIndex(cwd: string): boolean;
|
|
15
|
-
/** Parse file paths from git add "Permission denied" / "unable to index file" errors.
|
|
16
|
-
* Matches lines like: error: open("data/PingDB.mdf"): Permission denied
|
|
17
|
-
* and: error: unable to index file 'data/PingDB.mdf' */
|
|
18
|
-
export declare function parseDeniedFiles(errText: string): string[];
|
|
19
|
-
/** Run a command and return success status */
|
|
20
|
-
export declare function runCommand(cmd: string, args: string[], options?: {
|
|
21
|
-
silent?: boolean;
|
|
22
|
-
verbose?: boolean;
|
|
23
|
-
showCommand?: boolean;
|
|
24
|
-
cwd?: string;
|
|
25
|
-
}): {
|
|
26
|
-
success: boolean;
|
|
27
|
-
output: string;
|
|
28
|
-
stderr: string;
|
|
29
|
-
};
|
|
30
|
-
/** Extract GitHub repo identifier from repository field */
|
|
31
|
-
export declare function getGitHubRepo(pkg: any): string | null;
|
|
32
|
-
/** Run a command and throw on failure */
|
|
33
|
-
export declare function runCommandOrThrow(cmd: string, args: string[], options?: {
|
|
34
|
-
silent?: boolean;
|
|
35
|
-
cwd?: string;
|
|
36
|
-
}): string;
|
|
37
|
-
export declare function getGitStatus(cwd: string): GitStatus;
|
|
38
|
-
/** Validate package.json for release */
|
|
39
|
-
export declare function validatePackageJson(pkg: any): string[];
|
|
40
|
-
/** Get the latest git tag (if any) */
|
|
41
|
-
export declare function getLatestGitTag(cwd: string): string | null;
|
|
42
|
-
/** Check if a git tag exists */
|
|
43
|
-
export declare function gitTagExists(cwd: string, tag: string): boolean;
|
|
44
|
-
/** Delete a git tag */
|
|
45
|
-
export declare function deleteGitTag(cwd: string, tag: string): boolean;
|
|
46
|
-
/** Get all git tags */
|
|
47
|
-
export declare function getAllGitTags(cwd: string): string[];
|
|
48
|
-
/** Parse version from tag (e.g., 'v1.2.3' -> [1, 2, 3]) */
|
|
49
|
-
export declare function parseVersionTag(tag: string): number[] | null;
|
|
50
|
-
/** Compare two version arrays (returns -1 if a < b, 0 if equal, 1 if a > b) */
|
|
51
|
-
export declare function compareVersions(a: number[], b: number[]): number;
|
|
52
|
-
/** Fix version/tag mismatches */
|
|
53
|
-
export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
|
|
54
|
-
/** Wait for a package version to appear on the npm registry.
|
|
55
|
-
* First-time publishes (brand-new package name) take much longer to
|
|
56
|
-
* propagate than version bumps — npm has no cached metadata to update,
|
|
57
|
-
* so the registry/CDN can take several minutes before the package is
|
|
58
|
-
* resolvable. We wait longer and re-probe `npm view` for the version
|
|
59
|
-
* string until it shows up (or we hit the cap). */
|
|
60
|
-
export declare function waitForNpmVersion(pkgName: string, version: string, isNewPackage?: boolean, maxWaitMs?: number): boolean;
|
|
61
|
-
/** Run npm install -g with retries for registry propagation delay.
|
|
62
|
-
* Brand-new packages (first-time publish) take much longer to become
|
|
63
|
-
* installable than version bumps, so we use longer waits and more
|
|
64
|
-
* attempts when `isNewPackage` is true. */
|
|
65
|
-
export declare function installGlobalWithRetry(pkgSpec: string, cwd: string, isNewPackage?: boolean, maxRetries?: number): {
|
|
66
|
-
success: boolean;
|
|
67
|
-
output: string;
|
|
68
|
-
stderr: string;
|
|
69
|
-
};
|
|
70
|
-
/** Detect OAuth credentials.json type: "installed" (public app ID, safe to include) or "web" (has real secret, must ignore).
|
|
71
|
-
* Returns null if no credentials.json exists or it can't be parsed. */
|
|
72
|
-
export declare function detectCredentialsType(cwd: string): 'installed' | 'web' | null;
|
|
73
|
-
interface PushProtectionSecret {
|
|
74
|
-
type: string;
|
|
75
|
-
file: string;
|
|
76
|
-
unblockUrl: string;
|
|
77
|
-
}
|
|
78
|
-
interface PushProtectionInfo {
|
|
79
|
-
detected: boolean;
|
|
80
|
-
secrets: PushProtectionSecret[];
|
|
81
|
-
allInstalledOAuth: boolean;
|
|
82
|
-
}
|
|
83
|
-
/** Parse GitHub push protection (GH013) error output and extract secret details + unblock URLs. */
|
|
84
|
-
export declare function parsePushProtection(errorOutput: string, cwd: string): PushProtectionInfo;
|
|
85
|
-
/** Display push protection guidance based on the type of secrets detected. */
|
|
86
|
-
export declare function showPushProtectionGuidance(ppInfo: PushProtectionInfo): void;
|
|
87
|
-
/** Push to git with push-protection detection and auto-bypass for installed OAuth.
|
|
88
|
-
* Returns true if push succeeded (possibly after auto-bypass). */
|
|
89
|
-
export declare function pushWithProtection(cwd: string, verbose: boolean): boolean;
|
|
90
|
-
export {};
|
|
91
|
-
//# sourceMappingURL=git.d.ts.map
|