@nqlib/nqui 0.5.3 → 0.5.5

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/dist/styles.css CHANGED
@@ -75,7 +75,7 @@
75
75
 
76
76
 
77
77
  /* Custom dark mode variant - matches when .dark class is on element or ancestor */
78
- /* Non-dark themes — .light (warm paper) and .mid (dimmer neutral); both use light: utilities */
78
+ /* Non-dark theme — .light (warm paper) uses light: utilities */
79
79
  /* Hit-area — https://bazza.dev/craft/2026/hit-area */
80
80
  /* Bazza hit-area utilities — https://bazza.dev/craft/2026/hit-area */
81
81
 
@@ -792,14 +792,11 @@
792
792
  nqui Additional System Variables
793
793
  ============================================ */
794
794
 
795
- /*
796
- * Non-dark surfaces: /* ============================================
795
+ /* Default light theme variables (.dark block follows) */
796
+ /* ============================================
797
797
  nqui Color System & Design Tokens
798
798
  ============================================ */
799
799
 
800
- :root + html.light = warm paper; html.mid = separate token block (showcase / comparison).
801
- * Dark = .dark. Showcase app uses next-themes classes light | mid | dark.
802
- */
803
800
  :root {
804
801
  /* ============================================
805
802
  Z-Index Elevation Scale
@@ -1029,18 +1026,18 @@
1029
1026
  /* Light / system UI — warm paper */
1030
1027
  --background: oklch(0.985 0.002 78);
1031
1028
  --foreground: oklch(0.2416 0.0219 57);
1032
- --card: oklch(0.9792 0.0042 78.3);
1029
+ --card: oklch(0.977 0.004 78);
1033
1030
  --card-foreground: oklch(0.2416 0.0219 57);
1034
- --popover: oklch(0.9792 0.0042 78.3);
1031
+ --popover: oklch(0.977 0.004 78);
1035
1032
  --popover-foreground: oklch(0.2416 0.0219 57);
1036
- --secondary: oklch(0.927 0.009 73.15);
1033
+ --secondary: oklch(0.928 0.008 78);
1037
1034
  --secondary-foreground: oklch(0.3067 0.0309 56.81);
1038
- --muted: oklch(0.9111 0.0082 73.15);
1035
+ --muted: oklch(0.914 0.007 78);
1039
1036
  --muted-foreground: oklch(0.5576 0.0222 57.81);
1040
- --accent: oklch(0.935 0.011 73.15);
1037
+ --accent: oklch(0.936 0.007 78);
1041
1038
  --accent-foreground: oklch(0.2416 0.0219 57);
1042
- --border: oklch(0.89 0.0137 73.1);
1043
- --input: oklch(0.89 0.0137 73.1);
1039
+ --border: oklch(0.892 0.006 78);
1040
+ --input: oklch(0.892 0.006 78);
1044
1041
 
1045
1042
  /* Chart colors */
1046
1043
  --chart-1: oklch(0.809 0.105 251.813);
@@ -1053,48 +1050,13 @@
1053
1050
  --radius: 0.45rem;
1054
1051
 
1055
1052
  /* Sidebar */
1056
- --sidebar: oklch(0.9792 0.0042 78.3);
1053
+ --sidebar: oklch(0.977 0.004 78);
1057
1054
  --sidebar-foreground: oklch(0.2416 0.0219 57);
1058
1055
  --sidebar-primary: var(--primary);
1059
1056
  --sidebar-primary-foreground: var(--primary-foreground);
1060
- --sidebar-accent: oklch(0.96 0.004 75);
1057
+ --sidebar-accent: oklch(0.962 0.006 78);
1061
1058
  --sidebar-accent-foreground: oklch(0.205 0.015 55);
1062
- --sidebar-border: oklch(0.89 0.0137 73.1);
1063
- --sidebar-ring: var(--ring);
1064
- }
1065
-
1066
- /* Mid — between warm paper and dark (neutral, slightly dimmer than light) */
1067
- .mid {
1068
- --background: oklch(0.915 0.01 78);
1069
- --foreground: oklch(0.28 0.022 57);
1070
- --card: oklch(0.895 0.012 78);
1071
- --card-foreground: oklch(0.28 0.022 57);
1072
- --popover: oklch(0.895 0.012 78);
1073
- --popover-foreground: oklch(0.28 0.022 57);
1074
- --secondary: oklch(0.88 0.012 73);
1075
- --secondary-foreground: oklch(0.32 0.028 57);
1076
- --muted: oklch(0.855 0.012 73);
1077
- --muted-foreground: oklch(0.48 0.024 58);
1078
- --accent: oklch(0.87 0.014 73);
1079
- --accent-foreground: oklch(0.28 0.022 57);
1080
- --border: oklch(0.82 0.016 73);
1081
- --input: oklch(0.82 0.016 73);
1082
-
1083
- --chart-1: oklch(0.809 0.105 251.813);
1084
- --chart-2: oklch(0.623 0.214 259.815);
1085
- --chart-3: oklch(0.546 0.245 262.881);
1086
- --chart-4: oklch(0.488 0.243 264.376);
1087
- --chart-5: oklch(0.424 0.199 265.638);
1088
-
1089
- --radius: 0.45rem;
1090
-
1091
- --sidebar: oklch(0.9 0.01 78);
1092
- --sidebar-foreground: oklch(0.28 0.022 57);
1093
- --sidebar-primary: var(--primary);
1094
- --sidebar-primary-foreground: var(--primary-foreground);
1095
- --sidebar-accent: oklch(0.875 0.012 75);
1096
- --sidebar-accent-foreground: oklch(0.26 0.018 55);
1097
- --sidebar-border: oklch(0.82 0.016 73);
1059
+ --sidebar-border: oklch(0.892 0.006 78);
1098
1060
  --sidebar-ring: var(--ring);
1099
1061
  }
1100
1062
 
@@ -1180,14 +1142,14 @@
1180
1142
  --card-foreground: oklch(0.92 0 0);
1181
1143
  --popover: oklch(0.205 0 0);
1182
1144
  --popover-foreground: oklch(0.92 0 0);
1183
- --secondary: oklch(0.274 0.006 286.033);
1145
+ --secondary: oklch(0.274 0.003 0);
1184
1146
  --secondary-foreground: oklch(0.92 0 0);
1185
- --muted: oklch(0.24 0 0);
1147
+ --muted: oklch(0.24 0.002 0);
1186
1148
  --muted-foreground: oklch(0.708 0 0);
1187
- --accent: oklch(0.27 0 0);
1149
+ --accent: oklch(0.27 0.003 0);
1188
1150
  --accent-foreground: oklch(0.92 0 0);
1189
- --border: oklch(1 0 0 / 10%);
1190
- --input: oklch(1 0 0 / 0.07);
1151
+ --border: oklch(0.34 0.004 0);
1152
+ --input: oklch(0.30 0.003 0);
1191
1153
 
1192
1154
  /* Chart colors (same for both modes) */
1193
1155
  --chart-1: oklch(0.809 0.105 251.813);
@@ -1197,17 +1159,32 @@
1197
1159
  --chart-5: oklch(0.424 0.199 265.638);
1198
1160
 
1199
1161
  /* Sidebar for dark mode */
1200
- --sidebar: oklch(0.205 0 0);
1162
+ --sidebar: oklch(0.205 0.001 0);
1201
1163
  --sidebar-foreground: oklch(0.92 0 0);
1202
1164
  --sidebar-primary: var(--primary);
1203
1165
  --sidebar-primary-foreground: var(--primary-foreground);
1204
- --sidebar-accent: oklch(0.269 0 0);
1166
+ --sidebar-accent: oklch(0.269 0.003 0);
1205
1167
  --sidebar-accent-foreground: oklch(0.92 0 0);
1206
- --sidebar-border: oklch(1 0 0 / 10%);
1168
+ --sidebar-border: oklch(0.34 0.004 0);
1207
1169
  --sidebar-ring: oklch(0.556 0 0);
1208
1170
  }
1209
1171
 
1210
- /*
1211
- * html.light — next-themes; surface tokens match :root (warm paper).
1212
- * html.mid next-themes; surfaces use .mid block above (distinct from light).
1213
- */
1172
+ /* Elevation component styles from elevation.css */
1173
+ /**
1174
+ * Card surface elevation semantic --border + light shadow stack.
1175
+ * Dark mode matches v0.4.x (no inset / multi-stack drop shadows).
1176
+ * Lives here with the elevation system (was JS-injected from card.tsx).
1177
+ */
1178
+ .nqui-card {
1179
+ border: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
1180
+ box-shadow:
1181
+ 0 1px 0 0 color-mix(in oklch, var(--border) 40%, transparent),
1182
+ 0 1px 2px 0 oklch(0.15 0 0 / 0.05);
1183
+ }
1184
+
1185
+ .dark .nqui-card {
1186
+ border: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
1187
+ box-shadow:
1188
+ 0 1px 0 0 color-mix(in oklch, var(--border) 40%, transparent),
1189
+ 0 1px 2px 0 oklch(0 0 0 / 0.3);
1190
+ }
@@ -1,8 +1,7 @@
1
1
  ---
2
-
3
- ## name: nqui
4
-
2
+ name: nqui
5
3
  description: Hub for nqui component library skills and impeccable design skills. Use /nqui-components, /nqui-design-system, /impeccable, /audit, or specific skill names to invoke them.
4
+ ---
6
5
 
7
6
  # nqui Skills (Hub)
8
7
 
@@ -14,7 +13,6 @@ Installed into `.cursor/nqui-skills/` via `npx @nqlib/nqui init-skills`.
14
13
 
15
14
  ## nqui Component Library
16
15
 
17
-
18
16
  | Skill | When to use |
19
17
  | ------------------------------- | ------------------------------------------------------------------ |
20
18
  | **nqui-components** | Implementing UI, choosing components, component props and examples |
@@ -24,14 +22,12 @@ Installed into `.cursor/nqui-skills/` via `npx @nqlib/nqui init-skills`.
24
22
  | **nqui-bundle-size** | Bundle size best practices when adding deps or creating packages |
25
23
  | **nqui-local-published-toggle** | Switching between local and published @nqlib/nqui |
26
24
 
27
-
28
25
  ---
29
26
 
30
27
  ## Impeccable Design Skills
31
28
 
32
29
  Design quality and anti-pattern detection. Run `/impeccable teach` first to establish design context.
33
30
 
34
-
35
31
  | Skill | When to use |
36
32
  | -------------- | ----------------------------------------------------------------------------- |
37
33
  | **impeccable** | Build distinctive, production-grade UI; avoid AI slop aesthetics |
@@ -52,7 +48,6 @@ Design quality and anti-pattern detection. Run `/impeccable teach` first to esta
52
48
  | **/critique** | Heuristic-based UI review |
53
49
  | **/overdrive** | Push good UI to excellent |
54
50
 
55
-
56
51
  ---
57
52
 
58
53
  ## Usage
@@ -62,4 +57,4 @@ Skills are invoked by name — e.g. `/nqui-components`, `/impeccable`, `/audit i
62
57
  For component questions, always read the relevant doc first:
63
58
 
64
59
  - `.cursor/nqui-skills/components/nqui-<name>.md` (after init-skills)
65
- - `node_modules/@nqlib/nqui/docs/components/nqui-<name>.md` (npm install)
60
+ - `node_modules/@nqlib/nqui/docs/components/nqui-<name>.md` (npm install)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqlib/nqui",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "A React component library with enhanced UI components and developer tools",
5
5
  "type": "module",
6
6
  "main": "./dist/nqui.cjs.js",
@@ -22,6 +22,23 @@ const elevationCssPath = join(projectRoot, 'src', 'styles', 'elevation.css');
22
22
  const hitAreaCssPath = join(projectRoot, 'src', 'styles', 'hit-area.css');
23
23
  const outputPath = join(projectRoot, 'dist', 'styles.css');
24
24
 
25
+ function extractLayerBlock(css, layerName) {
26
+ const layerStart = css.indexOf(`@layer ${layerName}`);
27
+ if (layerStart === -1) return '';
28
+ const firstBrace = css.indexOf('{', layerStart);
29
+ if (firstBrace === -1) return '';
30
+ let depth = 1;
31
+ let i = firstBrace + 1;
32
+ while (i < css.length && depth > 0) {
33
+ const ch = css[i];
34
+ if (ch === '{') depth++;
35
+ if (ch === '}') depth--;
36
+ i++;
37
+ }
38
+ if (depth !== 0) return '';
39
+ return css.slice(firstBrace + 1, i - 1).trim();
40
+ }
41
+
25
42
  function extractStandaloneCSS() {
26
43
  // Read the source CSS files
27
44
  if (!existsSync(indexCssPath)) {
@@ -47,15 +64,22 @@ function extractStandaloneCSS() {
47
64
 
48
65
  // Extract :root block from elevation.css (it's wrapped in @layer base)
49
66
  let elevationRootContent = '';
67
+ let elevationComponentContent = '';
50
68
  if (elevationCss) {
51
- const elevationRootMatch = elevationCss.match(/@layer base\s*\{\s*:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}\s*\}/s);
52
- if (elevationRootMatch) {
53
- elevationRootContent = elevationRootMatch[1].trim();
69
+ const elevationBaseLayer = extractLayerBlock(elevationCss, 'base');
70
+ if (elevationBaseLayer) {
71
+ const elevationRootMatch = elevationBaseLayer.match(/:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
72
+ if (elevationRootMatch) {
73
+ elevationRootContent = elevationRootMatch[1].trim();
74
+ }
54
75
  } else {
55
76
  // Fallback: try to extract without @layer base wrapper
56
77
  const rootMatch = elevationCss.match(/:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
57
78
  if (rootMatch) elevationRootContent = rootMatch[1].trim();
58
79
  }
80
+
81
+ // Keep component-level elevation classes (e.g. .nqui-card) in dist/styles.css.
82
+ elevationComponentContent = extractLayerBlock(elevationCss, 'components');
59
83
  }
60
84
 
61
85
  // Extract :root and .dark blocks from colors.css (they're wrapped in @layer base)
@@ -229,6 +253,10 @@ function extractStandaloneCSS() {
229
253
  combinedCss.slice(rootIndex);
230
254
  }
231
255
 
256
+ if (elevationComponentContent) {
257
+ combinedCss += '\n\n/* Elevation component styles from elevation.css */\n' + elevationComponentContent + '\n';
258
+ }
259
+
232
260
  // Clean up extra blank lines
233
261
  combinedCss = combinedCss.replace(/\n{4,}/g, '\n\n\n');
234
262
 
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { readFileSync, writeFileSync, existsSync } from 'fs';
9
- import { execSync } from 'child_process';
9
+ import { execSync, spawn } from 'child_process';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { dirname, join } from 'path';
12
12
 
@@ -16,116 +16,193 @@ const rootDir = join(__dirname, '..');
16
16
  const packageJsonPath = join(rootDir, 'package.json');
17
17
  const npmrcPath = join(rootDir, '.npmrc');
18
18
 
19
- // Read package.json
20
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
21
-
22
- // Save originals for restore
23
- const originalPublishConfig = packageJson.publishConfig;
24
- const originalDependencies = { ...packageJson.dependencies };
19
+ function printSslTroubleshooting() {
20
+ console.error(`
21
+ 💡 TLS / network (e.g. ERR_SSL_SSL\\TLS_ALERT_BAD_RECORD_MAC) is usually environmental:
22
+ Run publish again; transient failures often work on a second attempt
23
+ Disable VPN or try another network; corporate proxies and inspection break TLS
24
+ Update Node LTS; ensure no antivirus is MITM’ing npm traffic
25
+ • Retry later if registry.npmjs.com is flaking
26
+ • Set NPM_PUBLISH_RETRIES=5 for more attempts (default 3)
27
+ `);
28
+ }
25
29
 
26
- // Remove workspace:* deps (npm doesn't support workspace: protocol).
27
- // nqcode/nqappbuilder are dev/showcase-only and not bundled in the library.
28
- const workspaceDeps = ['@nqlib/nqcode', '@nqlib/nqappbuilder'];
29
- for (const name of workspaceDeps) {
30
- if (packageJson.dependencies?.[name] === 'workspace:*') {
31
- delete packageJson.dependencies[name];
32
- console.log(` Removed ${name} (workspace dep)`);
30
+ function isVersionAlreadyPublished(pkgName, version, cwd) {
31
+ try {
32
+ const result = execSync(`npm view ${pkgName}@${version} version --registry=https://registry.npmjs.com`, {
33
+ encoding: 'utf-8',
34
+ cwd,
35
+ stdio: ['ignore', 'pipe', 'pipe'],
36
+ }).trim();
37
+ return result === version;
38
+ } catch (error) {
39
+ const errText = `${error?.message || ''} ${error?.stderr?.toString?.() || ''}`;
40
+ if (errText.includes('404') || errText.includes('E404')) {
41
+ return false;
42
+ }
43
+ throw error;
33
44
  }
34
45
  }
35
46
 
36
- // Override publishConfig for npmjs.com
37
- packageJson.publishConfig = {
38
- registry: 'https://registry.npmjs.com',
39
- access: 'public'
40
- };
41
-
42
- // Write modified package.json
43
- writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
44
-
45
- // Handle .npmrc file if it exists
46
- let originalNpmrc = null;
47
- let npmrcExists = false;
48
-
49
- if (existsSync(npmrcPath)) {
50
- npmrcExists = true;
51
- originalNpmrc = readFileSync(npmrcPath, 'utf-8');
52
-
53
- // Remove or comment out scoped registry line that points to GitHub Packages
54
- const lines = originalNpmrc.split('\n');
55
- const modifiedLines = lines.map(line => {
56
- // Comment out scoped registry lines for @nqlib
57
- if (line.trim().startsWith('@nqlib:registry=')) {
58
- return `# ${line} # Temporarily disabled for npmjs.com publish`;
47
+ async function main() {
48
+ // Read package.json
49
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
50
+
51
+ // Save originals for restore
52
+ const originalPublishConfig = packageJson.publishConfig;
53
+ const originalDependencies = { ...packageJson.dependencies };
54
+
55
+ // Remove workspace:* deps (npm doesn't support workspace: protocol).
56
+ const workspaceDeps = ['@nqlib/nqcode', '@nqlib/nqappbuilder'];
57
+ for (const name of workspaceDeps) {
58
+ if (packageJson.dependencies?.[name] === 'workspace:*') {
59
+ delete packageJson.dependencies[name];
60
+ console.log(` Removed ${name} (workspace dep)`);
59
61
  }
60
- return line;
61
- });
62
+ }
62
63
 
63
- writeFileSync(npmrcPath, modifiedLines.join('\n'));
64
- console.log('📝 Temporarily modified .npmrc');
65
- }
64
+ packageJson.publishConfig = {
65
+ registry: 'https://registry.npmjs.com',
66
+ access: 'public',
67
+ };
66
68
 
67
- try {
68
- // Check if user is logged in to npm
69
- console.log('🔐 Checking npm authentication...');
70
- try {
71
- const whoami = execSync('npm whoami --registry=https://registry.npmjs.com', {
72
- encoding: 'utf-8',
73
- cwd: rootDir,
74
- env: {
75
- ...process.env,
76
- npm_config_registry: 'https://registry.npmjs.com'
69
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
70
+
71
+ let originalNpmrc = null;
72
+ let npmrcExists = false;
73
+
74
+ if (existsSync(npmrcPath)) {
75
+ npmrcExists = true;
76
+ originalNpmrc = readFileSync(npmrcPath, 'utf-8');
77
+ const lines = originalNpmrc.split('\n');
78
+ const modifiedLines = lines.map((line) => {
79
+ if (line.trim().startsWith('@nqlib:registry=')) {
80
+ return `# ${line} # Temporarily disabled for npmjs.com publish`;
77
81
  }
78
- }).trim();
79
- console.log(`✅ Logged in as: ${whoami}`);
80
- } catch (authError) {
81
- console.error(' Not logged in to npmjs.com');
82
- console.error('💡 Please run: npm login --registry=https://registry.npmjs.com');
83
- console.error('💡 Or create an access token at: https://www.npmjs.com/settings/YOUR_USERNAME/tokens');
84
- throw new Error('Authentication required. Please login to npmjs.com first.');
82
+ return line;
83
+ });
84
+ writeFileSync(npmrcPath, modifiedLines.join('\n'));
85
+ console.log('📝 Temporarily modified .npmrc');
85
86
  }
86
87
 
87
- console.log('📦 Publishing to npmjs.com...');
88
- execSync('npm publish --registry=https://registry.npmjs.com --access=public', {
89
- stdio: 'inherit',
90
- cwd: rootDir,
91
- env: {
88
+ try {
89
+ console.log('🔐 Checking npm authentication...');
90
+ try {
91
+ const whoami = execSync('npm whoami --registry=https://registry.npmjs.com', {
92
+ encoding: 'utf-8',
93
+ cwd: rootDir,
94
+ env: {
95
+ ...process.env,
96
+ npm_config_registry: 'https://registry.npmjs.com',
97
+ },
98
+ }).trim();
99
+ console.log(`✅ Logged in as: ${whoami}`);
100
+ } catch {
101
+ console.error('❌ Not logged in to npmjs.com');
102
+ console.error('💡 Please run: npm login --registry=https://registry.npmjs.com');
103
+ console.error(
104
+ '💡 Or create an access token at: https://www.npmjs.com/settings/YOUR_USERNAME/tokens'
105
+ );
106
+ throw new Error('Authentication required. Please login to npmjs.com first.');
107
+ }
108
+
109
+ const publishEnv = {
92
110
  ...process.env,
93
- // Force npm to use npmjs.com registry
94
- npm_config_registry: 'https://registry.npmjs.com'
111
+ npm_config_registry: 'https://registry.npmjs.com',
112
+ };
113
+
114
+ const pkgName = packageJson.name;
115
+ const pkgVersion = packageJson.version;
116
+ if (isVersionAlreadyPublished(pkgName, pkgVersion, rootDir)) {
117
+ throw new Error(
118
+ `Version ${pkgVersion} is already published for ${pkgName}. Bump package.json version before publishing (e.g. npm version patch --no-git-tag-version).`
119
+ );
95
120
  }
96
- });
97
- console.log('✅ Published to npmjs.com successfully!');
98
- } catch (error) {
99
- console.error('❌ Failed to publish to npmjs.com');
100
- if (error.message && error.message.includes('Authentication required')) {
101
- throw error;
102
- }
103
- if (error.status === 404 || (error.output && error.output.toString().includes('404'))) {
104
- console.error('💡 Package not found. This might be the first publish.');
105
- console.error('💡 Make sure the @nqlib scope exists on npmjs.com');
106
- console.error('💡 For scoped packages, you need to own the scope or be added to it');
107
- }
108
- if (error.status === 401 || (error.output && error.output.toString().includes('401'))) {
109
- console.error('💡 Authentication failed. Please login: npm login --registry=https://registry.npmjs.com');
110
- }
111
- throw error;
112
- } finally {
113
- // Restore original publishConfig and dependencies
114
- if (originalPublishConfig) {
115
- packageJson.publishConfig = originalPublishConfig;
116
- } else {
117
- delete packageJson.publishConfig;
118
- }
119
- if (originalDependencies) {
120
- packageJson.dependencies = originalDependencies;
121
- }
122
- writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
123
- console.log('🔄 Restored package.json');
124
121
 
125
- // Restore .npmrc if it existed
126
- if (npmrcExists && originalNpmrc !== null) {
127
- writeFileSync(npmrcPath, originalNpmrc);
128
- console.log('🔄 Restored .npmrc');
122
+ const maxAttempts = Math.min(
123
+ 5,
124
+ Math.max(1, parseInt(process.env.NPM_PUBLISH_RETRIES || '3', 10) || 3)
125
+ );
126
+
127
+ const runPublish = () =>
128
+ new Promise((resolve, reject) => {
129
+ const child = spawn(
130
+ 'npm',
131
+ ['publish', '--registry=https://registry.npmjs.com', '--access', 'public'],
132
+ { stdio: 'inherit', cwd: rootDir, env: publishEnv, shell: false }
133
+ );
134
+ child.on('error', reject);
135
+ child.on('close', (code) => {
136
+ if (code === 0) resolve();
137
+ else {
138
+ const err = new Error(`npm publish exited with code ${code}`);
139
+ err.status = code;
140
+ reject(err);
141
+ }
142
+ });
143
+ });
144
+
145
+ console.log('📦 Publishing to npmjs.com...');
146
+ let lastError;
147
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
148
+ try {
149
+ await runPublish();
150
+ lastError = null;
151
+ break;
152
+ } catch (e) {
153
+ lastError = e;
154
+ if (attempt < maxAttempts) {
155
+ const wait = attempt * 2000;
156
+ console.warn(
157
+ `⚠️ Publish failed (attempt ${attempt}/${maxAttempts}). Retrying in ${wait / 1000}s (transient TLS/network often succeeds on retry)…`
158
+ );
159
+ await new Promise((r) => setTimeout(r, wait));
160
+ }
161
+ }
162
+ }
163
+ if (lastError) throw lastError;
164
+ console.log('✅ Published to npmjs.com successfully!');
165
+ } catch (error) {
166
+ console.error('❌ Failed to publish to npmjs.com');
167
+ if (error.message && error.message.includes('Authentication required')) {
168
+ throw error;
169
+ }
170
+ if (error.message && error.message.includes('already published')) {
171
+ console.error(`💡 ${error.message}`);
172
+ throw error;
173
+ }
174
+ const errText = `${error.message || ''} ${(error.output && error.output.toString()) || ''}`;
175
+ if (error.status === 404 || errText.includes('404')) {
176
+ console.error('💡 Package not found. This might be the first publish.');
177
+ console.error('💡 Make sure the @nqlib scope exists on npmjs.com');
178
+ console.error('💡 For scoped packages, you need to own the scope or be added to it');
179
+ }
180
+ if (error.status === 401 || errText.includes('401')) {
181
+ console.error(
182
+ '💡 Authentication failed. Please login: npm login --registry=https://registry.npmjs.com'
183
+ );
184
+ }
185
+ printSslTroubleshooting();
186
+ throw error;
187
+ } finally {
188
+ if (originalPublishConfig) {
189
+ packageJson.publishConfig = originalPublishConfig;
190
+ } else {
191
+ delete packageJson.publishConfig;
192
+ }
193
+ if (originalDependencies) {
194
+ packageJson.dependencies = originalDependencies;
195
+ }
196
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
197
+ console.log('🔄 Restored package.json');
198
+ if (npmrcExists && originalNpmrc !== null) {
199
+ writeFileSync(npmrcPath, originalNpmrc);
200
+ console.log('🔄 Restored .npmrc');
201
+ }
129
202
  }
130
203
  }
131
204
 
205
+ main().catch((e) => {
206
+ if (e && e.message) console.error(e.message);
207
+ process.exit(1);
208
+ });