@bobfrankston/npmglobalize 1.0.170 → 1.0.172

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.
Files changed (3) hide show
  1. package/lib.d.ts +18 -0
  2. package/lib.js +133 -0
  3. package/package.json +1 -1
package/lib.d.ts CHANGED
@@ -171,6 +171,24 @@ export declare function checkVersionExists(packageName: string, version: string)
171
171
  export declare function checkPackageExists(packageName: string): boolean;
172
172
  /** Check npm package access level (public/restricted/null if not published) */
173
173
  export declare function checkNpmAccess(packageName: string): 'public' | 'restricted' | null;
174
+ /** Walk `file:` deps transitively from a starting directory and ensure each is set
175
+ * up to be installable from a public consumer: persists `npmVisibility:"public"` in
176
+ * each dep's `.globalize.json5`, and flips npm access from `restricted` to `public`
177
+ * for any dep that's already on npm. Returns blockers for deps that can't be made
178
+ * public (private:true or noPublish:true). Used by single-package `-public-deps`
179
+ * to handle deps that are unchanged locally — those would otherwise be skipped by
180
+ * the cascade and stay private on npm. */
181
+ export declare function cascadePublicVisibility(cwd: string, opts?: {
182
+ dryRun?: boolean;
183
+ verbose?: boolean;
184
+ }): Promise<{
185
+ promoted: string[];
186
+ configWritten: string[];
187
+ blockers: Array<{
188
+ name: string;
189
+ reason: string;
190
+ }>;
191
+ }>;
174
192
  /** Check if public package has private/inaccessible dependencies */
175
193
  export declare function checkPrivateDependencies(pkg: any, verbose?: boolean): {
176
194
  name: string;
package/lib.js CHANGED
@@ -565,6 +565,113 @@ export function checkNpmAccess(packageName) {
565
565
  return null;
566
566
  }
567
567
  }
568
+ /** Walk `file:` deps transitively from a starting directory and ensure each is set
569
+ * up to be installable from a public consumer: persists `npmVisibility:"public"` in
570
+ * each dep's `.globalize.json5`, and flips npm access from `restricted` to `public`
571
+ * for any dep that's already on npm. Returns blockers for deps that can't be made
572
+ * public (private:true or noPublish:true). Used by single-package `-public-deps`
573
+ * to handle deps that are unchanged locally — those would otherwise be skipped by
574
+ * the cascade and stay private on npm. */
575
+ export async function cascadePublicVisibility(cwd, opts = {}) {
576
+ const { dryRun = false, verbose = false } = opts;
577
+ const promoted = [];
578
+ const configWritten = [];
579
+ const blockers = [];
580
+ const visited = new Set();
581
+ const processed = new Set(); // dedupe by package name across diamond deps
582
+ async function walk(dir) {
583
+ let real;
584
+ try {
585
+ real = fs.realpathSync(dir);
586
+ }
587
+ catch {
588
+ return;
589
+ }
590
+ if (visited.has(real))
591
+ return;
592
+ visited.add(real);
593
+ let pkg;
594
+ try {
595
+ pkg = readPackageJson(dir);
596
+ }
597
+ catch {
598
+ return;
599
+ }
600
+ for (const key of DEP_KEYS) {
601
+ // Use the .dependencies backup if present (has original file: refs).
602
+ const deps = pkg['.' + key] || pkg[key];
603
+ if (!deps)
604
+ continue;
605
+ for (const [name, value] of Object.entries(deps)) {
606
+ if (typeof value !== 'string' || !isFileRef(value))
607
+ continue;
608
+ let targetPath;
609
+ try {
610
+ targetPath = resolveFilePath(value, dir);
611
+ }
612
+ catch {
613
+ continue;
614
+ }
615
+ if (!fs.existsSync(path.join(targetPath, 'package.json')))
616
+ continue;
617
+ let targetPkg;
618
+ try {
619
+ targetPkg = readPackageJson(targetPath);
620
+ }
621
+ catch {
622
+ continue;
623
+ }
624
+ const targetName = targetPkg.name || name;
625
+ if (!processed.has(targetName)) {
626
+ processed.add(targetName);
627
+ if (targetPkg.private) {
628
+ blockers.push({ name: targetName, reason: '"private": true in package.json (cannot publish to npm)' });
629
+ }
630
+ else {
631
+ const targetConfig = readConfig(targetPath);
632
+ if (targetConfig.noPublish) {
633
+ blockers.push({ name: targetName, reason: 'noPublish:true in .globalize.json5' });
634
+ }
635
+ else {
636
+ if (targetConfig.npmVisibility !== 'public') {
637
+ if (!dryRun) {
638
+ targetConfig.npmVisibility = 'public';
639
+ writeConfig(targetPath, targetConfig, new Set(['npmVisibility']));
640
+ }
641
+ configWritten.push(targetName);
642
+ if (verbose)
643
+ console.log(colors.dim(` ${targetName}: npmVisibility:"public" written to .globalize.json5`));
644
+ }
645
+ const access = checkNpmAccess(targetName);
646
+ if (access === 'restricted') {
647
+ if (!dryRun) {
648
+ const r = runCommand('npm', ['access', 'set', 'status=public', targetName], { cwd: targetPath, silent: true });
649
+ if (r.success) {
650
+ promoted.push(targetName);
651
+ console.log(colors.green(` ✓ Flipped ${targetName} to PUBLIC on npm`));
652
+ }
653
+ else {
654
+ blockers.push({ name: targetName, reason: `npm access set failed: ${r.stderr || r.output || 'unknown'}` });
655
+ }
656
+ }
657
+ else {
658
+ console.log(colors.dim(` [dry-run] Would flip ${targetName} to PUBLIC on npm`));
659
+ }
660
+ }
661
+ else if (access === 'public' && verbose) {
662
+ console.log(colors.dim(` ${targetName}: already PUBLIC on npm`));
663
+ }
664
+ // access === null: not on npm yet — the cascade publish step handles that.
665
+ }
666
+ }
667
+ }
668
+ await walk(targetPath);
669
+ }
670
+ }
671
+ }
672
+ await walk(cwd);
673
+ return { promoted, configWritten, blockers };
674
+ }
568
675
  /** Check if public package has private/inaccessible dependencies */
569
676
  export function checkPrivateDependencies(pkg, verbose = false) {
570
677
  const privateDeps = [];
@@ -3844,6 +3951,32 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3844
3951
  console.log(colors.dim(`Would add publishConfig.access: "public" to package.json`));
3845
3952
  }
3846
3953
  }
3954
+ // -public-deps pre-pass: at the top of the user-invoked call, walk file: deps
3955
+ // transitively and flip any that are already on npm but currently `restricted`
3956
+ // to `public`, persisting `npmVisibility:"public"` in each dep's .globalize.json5.
3957
+ // This handles the case where a public consumer's deps are unchanged locally
3958
+ // (so the cascade publish step would skip them) but published as private —
3959
+ // public installers would 404. Only runs once at the top level (suppressed in
3960
+ // recursive cascade and workspace-orchestrated calls, which already handle this).
3961
+ if (publicDeps && !options._fromDep && !options._fromWorkspace) {
3962
+ console.log(colors.italic('-public-deps: walking file: deps to ensure all are PUBLIC on npm...'));
3963
+ const result = await cascadePublicVisibility(cwd, { dryRun, verbose });
3964
+ if (result.promoted.length === 0 && result.configWritten.length === 0 && verbose) {
3965
+ console.log(colors.dim(' (no changes needed)'));
3966
+ }
3967
+ if (result.blockers.length > 0) {
3968
+ console.log('');
3969
+ console.log(colors.red('✗ -public-deps: cannot make every transitive dep public:'));
3970
+ for (const b of result.blockers) {
3971
+ console.log(colors.red(` ${b.name}: ${b.reason}`));
3972
+ }
3973
+ if (!force) {
3974
+ return false;
3975
+ }
3976
+ console.log(colors.yellow('Continuing with --force despite blockers...'));
3977
+ }
3978
+ console.log('');
3979
+ }
3847
3980
  // Check for private dependencies in public packages
3848
3981
  const willBePublic = effectiveNpmVisibility === 'public' ||
3849
3982
  currentAccess === 'public' ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.170",
3
+ "version": "1.0.172",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",