@smoothbricks/cli 0.10.3 → 0.10.4

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 (111) hide show
  1. package/README.md +9 -13
  2. package/dist/bun/preload.d.ts +13 -0
  3. package/dist/bun/preload.d.ts.map +1 -0
  4. package/dist/bun/preload.js +12 -0
  5. package/dist/cli.js +12 -4
  6. package/dist/generate/index.d.ts.map +1 -1
  7. package/dist/generate/index.js +0 -6
  8. package/dist/github-ci/index.d.ts.map +1 -1
  9. package/dist/github-ci/index.js +52 -16
  10. package/dist/github-ci/outputs.d.ts.map +1 -1
  11. package/dist/github-ci/outputs.js +1 -2
  12. package/dist/lib/cli-package.js +2 -2
  13. package/dist/lib/json.d.ts +118 -11
  14. package/dist/lib/json.d.ts.map +1 -1
  15. package/dist/lib/json.js +317 -29
  16. package/dist/lib/workspace.d.ts +7 -14
  17. package/dist/lib/workspace.d.ts.map +1 -1
  18. package/dist/lib/workspace.js +76 -82
  19. package/dist/monorepo/ci-workflow.d.ts +2 -0
  20. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  21. package/dist/monorepo/ci-workflow.js +27 -1
  22. package/dist/monorepo/git-config.js +2 -2
  23. package/dist/monorepo/index.d.ts +3 -1
  24. package/dist/monorepo/index.d.ts.map +1 -1
  25. package/dist/monorepo/index.js +6 -2
  26. package/dist/monorepo/lockfile.d.ts +13 -1
  27. package/dist/monorepo/lockfile.d.ts.map +1 -1
  28. package/dist/monorepo/lockfile.js +33 -21
  29. package/dist/monorepo/managed-files.d.ts +11 -0
  30. package/dist/monorepo/managed-files.d.ts.map +1 -1
  31. package/dist/monorepo/managed-files.js +52 -32
  32. package/dist/monorepo/package-policy.d.ts.map +1 -1
  33. package/dist/monorepo/package-policy.js +52 -45
  34. package/dist/monorepo/packed-manifest.d.ts +5 -2
  35. package/dist/monorepo/packed-manifest.d.ts.map +1 -1
  36. package/dist/monorepo/packed-manifest.js +16 -47
  37. package/dist/monorepo/packed-package.d.ts.map +1 -1
  38. package/dist/monorepo/packed-package.js +28 -16
  39. package/dist/monorepo/packs/index.d.ts.map +1 -1
  40. package/dist/monorepo/packs/index.js +13 -4
  41. package/dist/monorepo/publish-workflow.js +3 -1
  42. package/dist/monorepo/runtime.d.ts +15 -0
  43. package/dist/monorepo/runtime.d.ts.map +1 -1
  44. package/dist/monorepo/runtime.js +66 -12
  45. package/dist/monorepo/tool-validation.d.ts.map +1 -1
  46. package/dist/monorepo/tool-validation.js +32 -23
  47. package/dist/monorepo/wrangler.d.ts.map +1 -1
  48. package/dist/monorepo/wrangler.js +13 -13
  49. package/dist/nx/index.d.ts +9 -7
  50. package/dist/nx/index.d.ts.map +1 -1
  51. package/dist/nx/index.js +48 -31
  52. package/dist/pr/index.d.ts.map +1 -1
  53. package/dist/pr/index.js +28 -32
  54. package/dist/release/candidates.d.ts +3 -2
  55. package/dist/release/candidates.d.ts.map +1 -1
  56. package/dist/release/candidates.js +4 -5
  57. package/dist/release/index.d.ts +1 -0
  58. package/dist/release/index.d.ts.map +1 -1
  59. package/dist/release/index.js +82 -59
  60. package/dist/wrangler/prepare-env.d.ts +0 -1
  61. package/dist/wrangler/prepare-env.d.ts.map +1 -1
  62. package/dist/wrangler/prepare-env.js +100 -51
  63. package/dist/wrangler/scaffold.d.ts.map +1 -1
  64. package/dist/wrangler/scaffold.js +12 -12
  65. package/managed/raw/git-format-staged.yml +19 -1
  66. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +2 -1
  67. package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
  68. package/managed/raw/tooling/direnv/setup-environment.ts +150 -19
  69. package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
  70. package/managed/templates/github/actions/cache-node-modules/action.yml +5 -2
  71. package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
  72. package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
  73. package/managed/templates/github/workflows/managed-files.yml +80 -0
  74. package/package.json +15 -4
  75. package/src/bun/preload.ts +12 -0
  76. package/src/cli.ts +19 -7
  77. package/src/generate/index.ts +0 -6
  78. package/src/github-ci/index.ts +49 -18
  79. package/src/github-ci/outputs.ts +1 -2
  80. package/src/lib/cli-package.ts +2 -2
  81. package/src/lib/json.ts +291 -32
  82. package/src/lib/workspace.ts +88 -100
  83. package/src/monorepo/__tests__/ci-workflow.test.ts +3 -3
  84. package/src/monorepo/ci-workflow.ts +27 -1
  85. package/src/monorepo/git-config.ts +2 -2
  86. package/src/monorepo/index.ts +6 -2
  87. package/src/monorepo/lockfile.test.ts +35 -36
  88. package/src/monorepo/lockfile.ts +48 -24
  89. package/src/monorepo/managed-files.test.ts +1 -0
  90. package/src/monorepo/managed-files.ts +57 -38
  91. package/src/monorepo/package-policy.test.ts +20 -50
  92. package/src/monorepo/package-policy.ts +76 -59
  93. package/src/monorepo/packed-manifest.ts +18 -52
  94. package/src/monorepo/packed-package.ts +43 -22
  95. package/src/monorepo/packs/index.ts +13 -4
  96. package/src/monorepo/publish-workflow.ts +3 -1
  97. package/src/monorepo/runtime.test.ts +42 -3
  98. package/src/monorepo/runtime.ts +90 -12
  99. package/src/monorepo/tool-validation.ts +35 -25
  100. package/src/monorepo/wrangler.test.ts +11 -15
  101. package/src/monorepo/wrangler.ts +15 -15
  102. package/src/nx/index.test.ts +9 -5
  103. package/src/nx/index.ts +69 -42
  104. package/src/pr/index.ts +42 -36
  105. package/src/release/__tests__/candidates.test.ts +5 -4
  106. package/src/release/__tests__/trust-publisher.test.ts +7 -0
  107. package/src/release/candidates.ts +10 -17
  108. package/src/release/index.ts +104 -73
  109. package/src/wrangler/prepare-env.ts +75 -54
  110. package/src/wrangler/scaffold.test.ts +9 -12
  111. package/src/wrangler/scaffold.ts +13 -13
package/src/nx/index.ts CHANGED
@@ -2,7 +2,13 @@ import { existsSync } from 'node:fs';
2
2
  import { lstat, rm } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { $ } from 'bun';
5
- import { isRecord, recordProperty } from '../lib/json.js';
5
+ import {
6
+ type NxDependsOn,
7
+ type NxProjectJson,
8
+ type NxTargetConfig,
9
+ parseNxProjectJsonText,
10
+ parseStringArrayText,
11
+ } from '../lib/json.js';
6
12
  import { decode, run } from '../lib/run.js';
7
13
 
8
14
  export interface ProjectTargets {
@@ -33,100 +39,116 @@ export function nxCacheDirectories(root: string): string[] {
33
39
  return [join(root, '.nx/cache'), join(root, '.nx/workspace-data'), join(root, 'node_modules/.cache/nx')];
34
40
  }
35
41
 
36
- export function targetNamesFromNxProjectJson(value: unknown): string[] {
37
- const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
42
+ export function targetNamesFromNxProjectJson(value: NxProjectJson | null | undefined): string[] {
43
+ const targets = value?.targets;
38
44
  return targets ? Object.keys(targets).sort((a, b) => a.localeCompare(b)) : [];
39
45
  }
40
46
 
41
- export function projectRootFromNxProjectJson(value: unknown): string | undefined {
42
- return isRecord(value) && typeof value.root === 'string' ? value.root : undefined;
47
+ export function projectRootFromNxProjectJson(value: NxProjectJson | null | undefined): string | undefined {
48
+ return typeof value?.root === 'string' ? value.root : undefined;
43
49
  }
44
50
 
45
- export function buildDependsOnFromNxProjectJson(value: unknown): string[] | undefined {
51
+ export function buildDependsOnFromNxProjectJson(value: NxProjectJson | null | undefined): string[] | undefined {
46
52
  return targetDependenciesFromNxProjectJson(value).get('build');
47
53
  }
48
54
 
49
- export function targetDependenciesFromNxProjectJson(value: unknown): Map<string, string[]> {
50
- const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
55
+ export function targetDependenciesFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string[]> {
56
+ const targets = value?.targets;
51
57
  const dependencies = new Map<string, string[]>();
52
58
  if (!targets) {
53
59
  return dependencies;
54
60
  }
55
61
  for (const [targetName, target] of Object.entries(targets)) {
56
- if (!isRecord(target) || !Array.isArray(target.dependsOn)) {
62
+ if (!target.dependsOn) {
57
63
  continue;
58
64
  }
59
65
  const entries: string[] = [];
60
66
  for (const dependency of target.dependsOn) {
61
- if (typeof dependency === 'string') {
62
- entries.push(dependency);
67
+ const local = localDependsOnTarget(dependency);
68
+ if (local === undefined) {
69
+ if (isInvalidDependsOn(dependency)) {
70
+ throw new Error(`Nx target ${targetName} has an invalid dependsOn entry.`);
71
+ }
63
72
  continue;
64
73
  }
65
- if (!isRecord(dependency) || typeof dependency.target !== 'string') {
66
- throw new Error(`Nx target ${targetName} has an invalid dependsOn entry.`);
67
- }
68
- if (dependency.projects !== undefined && dependency.projects !== 'self') {
69
- throw new Error(`Nx target ${targetName} uses unsupported cross-project dependsOn for ${dependency.target}.`);
70
- }
71
- entries.push(dependency.target);
74
+ entries.push(local);
72
75
  }
73
76
  dependencies.set(targetName, entries);
74
77
  }
75
78
  return dependencies;
76
79
  }
77
80
 
78
- export function targetExecutorsFromNxProjectJson(value: unknown): Map<string, string> {
79
- const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
81
+ function localDependsOnTarget(dependency: NxDependsOn): string | undefined {
82
+ if (typeof dependency === 'string') {
83
+ return dependency;
84
+ }
85
+ if (typeof dependency.target !== 'string') {
86
+ return undefined;
87
+ }
88
+ if (dependency.projects !== undefined && dependency.projects !== 'self') {
89
+ // Target closures are intentionally project-local. Nx schedules explicit
90
+ // cross-project prerequisites itself; treating them as local targets
91
+ // would collect or verify outputs from the wrong project.
92
+ return undefined;
93
+ }
94
+ return dependency.target;
95
+ }
96
+
97
+ function isInvalidDependsOn(dependency: NxDependsOn): boolean {
98
+ return typeof dependency !== 'string' && typeof dependency.target !== 'string';
99
+ }
100
+
101
+ export function targetExecutorsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string> {
102
+ const targets = value?.targets;
80
103
  const executors = new Map<string, string>();
81
104
  if (!targets) {
82
105
  return executors;
83
106
  }
84
107
  for (const [targetName, target] of Object.entries(targets)) {
85
- if (isRecord(target) && typeof target.executor === 'string') {
108
+ if (typeof target.executor === 'string') {
86
109
  executors.set(targetName, target.executor);
87
110
  }
88
111
  }
89
112
  return executors;
90
113
  }
91
114
 
92
- export function targetOutputsFromNxProjectJson(value: unknown): Map<string, string[]> {
115
+ export function targetOutputsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string[]> {
93
116
  return targetStringArraysFromNxProjectJson(value, 'outputs');
94
117
  }
95
118
 
96
- export function targetScriptsFromNxProjectJson(value: unknown): Map<string, string> {
97
- const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
119
+ export function targetScriptsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string> {
120
+ const targets = value?.targets;
98
121
  const scripts = new Map<string, string>();
99
122
  if (!targets) {
100
123
  return scripts;
101
124
  }
102
125
  for (const [targetName, target] of Object.entries(targets)) {
103
- if (!isRecord(target)) {
104
- continue;
105
- }
106
- const options = recordProperty(target, 'options');
107
- if (typeof options?.script === 'string') {
108
- scripts.set(targetName, options.script);
126
+ if (typeof target.options?.script === 'string') {
127
+ scripts.set(targetName, target.options.script);
109
128
  }
110
129
  }
111
130
  return scripts;
112
131
  }
113
132
 
114
- function targetStringArraysFromNxProjectJson(value: unknown, property: string): Map<string, string[]> {
115
- const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
133
+ function targetStringArraysFromNxProjectJson(
134
+ value: NxProjectJson | null | undefined,
135
+ property: 'outputs' | 'inputs',
136
+ ): Map<string, string[]> {
137
+ const targets = value?.targets;
116
138
  const values = new Map<string, string[]>();
117
139
  if (!targets) {
118
140
  return values;
119
141
  }
120
142
  for (const [targetName, target] of Object.entries(targets)) {
121
- if (!isRecord(target)) {
122
- continue;
123
- }
124
143
  const entries = target[property];
125
144
  if (Array.isArray(entries)) {
126
- values.set(
127
- targetName,
128
- entries.filter((entry): entry is string => typeof entry === 'string'),
129
- );
145
+ const strings: string[] = [];
146
+ for (const entry of entries) {
147
+ if (typeof entry === 'string') {
148
+ strings.push(entry);
149
+ }
150
+ }
151
+ values.set(targetName, strings);
130
152
  }
131
153
  }
132
154
  return values;
@@ -154,8 +176,8 @@ export function projectNamesFromNxShowProjectsOutput(output: string): string[] {
154
176
 
155
177
  if (trimmed.startsWith('[')) {
156
178
  try {
157
- const parsed: unknown = JSON.parse(trimmed);
158
- if (Array.isArray(parsed) && parsed.every((entry) => typeof entry === 'string')) {
179
+ const parsed = parseStringArrayText(trimmed);
180
+ if (parsed) {
159
181
  return parsed.sort((a, b) => a.localeCompare(b));
160
182
  }
161
183
  } catch {
@@ -220,7 +242,10 @@ async function readNxProjectNames(root: string): Promise<string[]> {
220
242
  async function readProjectTarget(root: string, project: string): Promise<ProjectTargets> {
221
243
  const command = nxShowProjectCommand(project);
222
244
  const result = await $`${command.command} ${command.args}`.cwd(root).quiet();
223
- const parsed: unknown = JSON.parse(decode(result.stdout));
245
+ const parsed = parseNxProjectJsonText(decode(result.stdout));
246
+ if (!parsed) {
247
+ throw new Error(`Unable to inspect Nx project ${project}.`);
248
+ }
224
249
  const targetDependencies = targetDependenciesFromNxProjectJson(parsed);
225
250
  return {
226
251
  project,
@@ -233,3 +258,5 @@ async function readProjectTarget(root: string, project: string): Promise<Project
233
258
  targetScripts: targetScriptsFromNxProjectJson(parsed),
234
259
  };
235
260
  }
261
+
262
+ export type { NxProjectJson, NxTargetConfig };
package/src/pr/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
+ import typia from 'typia';
3
4
  import { formatMarkerHits, scanRefChangedForMarkers } from '../lib/conflict-markers.js';
4
- import { isRecord, readJson } from '../lib/json.js';
5
+ import { readJson } from '../lib/json.js';
5
6
  import { run, runResult } from '../lib/run.js';
6
7
 
7
8
  /** 0 = clean/done, 2 = action required (conflicts to resolve), 1 = usage/error. */
@@ -50,6 +51,29 @@ interface PrResolveState {
50
51
  startedAt: string;
51
52
  }
52
53
 
54
+ interface GhPrViewJson {
55
+ number: number;
56
+ url: string;
57
+ headRefName: string;
58
+ baseRefName: string;
59
+ isCrossRepository?: boolean;
60
+ }
61
+
62
+ interface PrResolveStateJson {
63
+ pr: number;
64
+ url?: string;
65
+ headBranch: string;
66
+ baseBranch: string;
67
+ remote: string;
68
+ crossRepo?: boolean;
69
+ originalBranch?: string;
70
+ originalSha?: string;
71
+ startedAt?: string;
72
+ }
73
+
74
+ const parseGhPrViewJson = typia.json.createIsParse<GhPrViewJson>();
75
+ const isPrResolveStateJson = typia.createIs<PrResolveStateJson>();
76
+
53
77
  /**
54
78
  * Agent-first conflict resolution for a GitHub PR.
55
79
  *
@@ -209,25 +233,16 @@ async function prMeta(shell: PrResolveShell, root: string, prArg: string): Promi
209
233
  if (result.exitCode !== 0) {
210
234
  throw new Error(`Could not resolve PR '${prArg}' via gh: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
211
235
  }
212
- const raw: unknown = JSON.parse(result.stdout);
213
- if (!isRecord(raw)) {
236
+ const raw = parseGhPrViewJson(result.stdout);
237
+ if (!raw) {
214
238
  throw new Error(`gh pr view returned unexpected JSON for '${prArg}'.`);
215
239
  }
216
- const { number, url, headRefName, baseRefName } = raw;
217
- if (
218
- typeof number !== 'number' ||
219
- typeof url !== 'string' ||
220
- typeof headRefName !== 'string' ||
221
- typeof baseRefName !== 'string'
222
- ) {
223
- throw new Error(`gh pr view JSON for '${prArg}' is missing expected fields.`);
224
- }
225
- const match = /github\.com\/([^/]+\/[^/]+)\/pull\//.exec(url);
240
+ const match = /github\.com\/([^/]+\/[^/]+)\/pull\//.exec(raw.url);
226
241
  return {
227
- number,
228
- url,
229
- headBranch: headRefName,
230
- baseBranch: baseRefName,
242
+ number: raw.number,
243
+ url: raw.url,
244
+ headBranch: raw.headRefName,
245
+ baseBranch: raw.baseRefName,
231
246
  crossRepo: raw.isCrossRepository === true,
232
247
  nameWithOwner: match ? match[1] : '',
233
248
  };
@@ -326,28 +341,19 @@ function readState(statePath: string): PrResolveState | null {
326
341
  } catch {
327
342
  return null;
328
343
  }
329
- if (!isRecord(raw)) {
330
- return null;
331
- }
332
- const { pr, url, headBranch, baseBranch, remote, crossRepo, originalBranch, originalSha, startedAt } = raw;
333
- if (
334
- typeof pr !== 'number' ||
335
- typeof headBranch !== 'string' ||
336
- typeof baseBranch !== 'string' ||
337
- typeof remote !== 'string'
338
- ) {
344
+ if (!isPrResolveStateJson(raw)) {
339
345
  return null;
340
346
  }
341
347
  return {
342
- pr,
343
- url: typeof url === 'string' ? url : '',
344
- headBranch,
345
- baseBranch,
346
- remote,
347
- crossRepo: crossRepo === true,
348
- originalBranch: typeof originalBranch === 'string' ? originalBranch : '',
349
- originalSha: typeof originalSha === 'string' ? originalSha : '',
350
- startedAt: typeof startedAt === 'string' ? startedAt : '',
348
+ pr: raw.pr,
349
+ url: raw.url ?? '',
350
+ headBranch: raw.headBranch,
351
+ baseBranch: raw.baseBranch,
352
+ remote: raw.remote,
353
+ crossRepo: raw.crossRepo === true,
354
+ originalBranch: raw.originalBranch ?? '',
355
+ originalSha: raw.originalSha ?? '',
356
+ startedAt: raw.startedAt ?? '',
351
357
  };
352
358
  }
353
359
 
@@ -2,7 +2,8 @@ import { describe, expect, it } from 'bun:test';
2
2
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { $ } from 'bun';
5
- import { isRecord } from '../../lib/json.js';
5
+ import type { PackageJson } from '../../lib/json.js';
6
+ import { parsePackageJsonText } from '../../lib/json.js';
6
7
  import { type AutoReleaseCandidateShell, autoReleaseCandidatePackages } from '../candidates.js';
7
8
  import type { ReleasePackageInfo } from '../core.js';
8
9
  import { git, tag, withFixtureRepo, writePackage } from './helpers/fixture-repo.js';
@@ -309,9 +310,9 @@ function gitCandidateShell(
309
310
  };
310
311
  }
311
312
 
312
- function parseJsonObject(text: string): Record<string, unknown> {
313
- const parsed = JSON.parse(text);
314
- if (!isRecord(parsed)) {
313
+ function parseJsonObject(text: string): PackageJson {
314
+ const parsed = parsePackageJsonText(text);
315
+ if (!parsed) {
315
316
  throw new Error('expected JSON object');
316
317
  }
317
318
  return parsed;
@@ -179,6 +179,13 @@ describe('trusted publisher setup', () => {
179
179
 
180
180
  it('recognizes npm trust access-denied output', () => {
181
181
  expect(npmTrustListAccessDenied('{"error":{"code":"E403"}}', '')).toBe(true);
182
+ expect(npmTrustListAccessDenied('', 'npm error code E401')).toBe(true);
183
+ expect(
184
+ npmTrustListAccessDenied(
185
+ '',
186
+ 'npm error 401 Unauthorized - GET https://registry.npmjs.org/-/package/@scope%2fpkg/trust - {"success":false,"error":"You must be logged in to publish packages."}',
187
+ ),
188
+ ).toBe(true);
182
189
  expect(npmTrustListAccessDenied('', 'npm error code EOTP')).toBe(false);
183
190
  });
184
191
 
@@ -1,4 +1,4 @@
1
- import { isRecord } from '../lib/json.js';
1
+ import type { PackageJson } from '../lib/json.js';
2
2
  import type { ReleasePackageInfo } from './core.js';
3
3
  import { releaseTag } from './core.js';
4
4
 
@@ -6,8 +6,8 @@ export interface AutoReleaseCandidateShell {
6
6
  gitRefExists(ref: string): Promise<boolean>;
7
7
  latestStableReleaseRef(projectName: string): Promise<string | null>;
8
8
  packageChangedFilesSince(ref: string, packagePath: string): Promise<string[]>;
9
- packageJsonAtRef(ref: string, packagePath: string): Promise<Record<string, unknown> | null>;
10
- currentPackageJson(packagePath: string): Promise<Record<string, unknown> | null>;
9
+ packageJsonAtRef(ref: string, packagePath: string): Promise<PackageJson | null>;
10
+ currentPackageJson(packagePath: string): Promise<PackageJson | null>;
11
11
  packageBuildInputPatterns(projectName: string, packagePath: string): Promise<string[]>;
12
12
  packageHasHistory(packagePath: string): Promise<boolean>;
13
13
  }
@@ -104,23 +104,16 @@ async function packageHasReleasableChangesSince<Package extends ReleasePackageIn
104
104
  return false;
105
105
  }
106
106
 
107
- function releasableManifestChanged(
108
- previousManifest: Record<string, unknown>,
109
- currentManifest: Record<string, unknown>,
110
- ): boolean {
107
+ function releasableManifestChanged(previousManifest: PackageJson, currentManifest: PackageJson): boolean {
111
108
  for (const key of releasableManifestKeys) {
112
- if (!stableJsonEqual(previousManifest[key], currentManifest[key])) {
109
+ if (!stableJsonEqual(Reflect.get(previousManifest, key), Reflect.get(currentManifest, key))) {
113
110
  return true;
114
111
  }
115
112
  }
116
113
  return false;
117
114
  }
118
115
 
119
- function isReleasablePackagePath(
120
- path: string,
121
- manifest: Record<string, unknown> | null,
122
- buildInputPatterns: string[],
123
- ): boolean {
116
+ function isReleasablePackagePath(path: string, manifest: PackageJson | null, buildInputPatterns: string[]): boolean {
124
117
  return (
125
118
  isBuildInputPath(path, buildInputPatterns) || isPackageMetadataPath(path) || isManifestFilesPath(path, manifest)
126
119
  );
@@ -195,13 +188,13 @@ function isPackageMetadataPath(path: string): boolean {
195
188
  return /^(README|LICENSE|CHANGELOG)(\.|$)/.test(path);
196
189
  }
197
190
 
198
- function isManifestFilesPath(path: string, manifest: Record<string, unknown> | null): boolean {
191
+ function isManifestFilesPath(path: string, manifest: PackageJson | null): boolean {
199
192
  const files = manifest?.files;
200
193
  if (!Array.isArray(files)) {
201
194
  return false;
202
195
  }
203
196
  for (const entry of files) {
204
- if (typeof entry !== 'string' || entry.length === 0 || entry.startsWith('!')) {
197
+ if (entry.length === 0 || entry.startsWith('!')) {
205
198
  continue;
206
199
  }
207
200
  if (matchesManifestFilesEntry(path, entry)) {
@@ -227,10 +220,10 @@ function stableJson(value: unknown): unknown {
227
220
  if (Array.isArray(value)) {
228
221
  return value.map(stableJson);
229
222
  }
230
- if (isRecord(value)) {
223
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
231
224
  const normalized: Record<string, unknown> = {};
232
225
  for (const key of Object.keys(value).sort()) {
233
- normalized[key] = stableJson(value[key]);
226
+ normalized[key] = stableJson(Reflect.get(value, key));
234
227
  }
235
228
  return normalized;
236
229
  }