@expo-harmony/expo-modules-autolinking 55.0.25-harmony.1

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 (103) hide show
  1. package/README.md +12 -0
  2. package/bin/expo-harmony-autolinking.ts +14 -0
  3. package/build/bin/expo-harmony-autolinking.d.ts +2 -0
  4. package/build/bin/expo-harmony-autolinking.js +11 -0
  5. package/build/index.d.ts +8 -0
  6. package/build/index.js +15 -0
  7. package/build/src/autolinking/link.d.ts +3 -0
  8. package/build/src/autolinking/link.js +150 -0
  9. package/build/src/autolinking/resolve.d.ts +4 -0
  10. package/build/src/autolinking/resolve.js +25 -0
  11. package/build/src/autolinking/search.d.ts +3 -0
  12. package/build/src/autolinking/search.js +357 -0
  13. package/build/src/autolinking/verify.d.ts +4 -0
  14. package/build/src/autolinking/verify.js +366 -0
  15. package/build/src/commands/autolinkingOptions.d.ts +12 -0
  16. package/build/src/commands/autolinkingOptions.js +35 -0
  17. package/build/src/commands/cli.d.ts +2 -0
  18. package/build/src/commands/cli.js +50 -0
  19. package/build/src/commands/link.d.ts +2 -0
  20. package/build/src/commands/link.js +27 -0
  21. package/build/src/commands/output.d.ts +3 -0
  22. package/build/src/commands/output.js +33 -0
  23. package/build/src/commands/resolve.d.ts +2 -0
  24. package/build/src/commands/resolve.js +13 -0
  25. package/build/src/commands/search.d.ts +2 -0
  26. package/build/src/commands/search.js +13 -0
  27. package/build/src/commands/verify.d.ts +2 -0
  28. package/build/src/commands/verify.js +17 -0
  29. package/build/src/config/constants.d.ts +17 -0
  30. package/build/src/config/constants.js +40 -0
  31. package/build/src/config/options.d.ts +13 -0
  32. package/build/src/config/options.js +202 -0
  33. package/build/src/errors.d.ts +18 -0
  34. package/build/src/errors.js +35 -0
  35. package/build/src/harmony/manifest/generate.d.ts +4 -0
  36. package/build/src/harmony/manifest/generate.js +55 -0
  37. package/build/src/harmony/manifest/index.d.ts +7 -0
  38. package/build/src/harmony/manifest/index.js +64 -0
  39. package/build/src/harmony/materialize.d.ts +12 -0
  40. package/build/src/harmony/materialize.js +278 -0
  41. package/build/src/harmony/ohpm/dependencies.d.ts +7 -0
  42. package/build/src/harmony/ohpm/dependencies.js +52 -0
  43. package/build/src/harmony/persistence/canonicalize.d.ts +15 -0
  44. package/build/src/harmony/persistence/canonicalize.js +108 -0
  45. package/build/src/harmony/providers/host.d.ts +3 -0
  46. package/build/src/harmony/providers/host.js +88 -0
  47. package/build/src/harmony/rnoh/cli.d.ts +2 -0
  48. package/build/src/harmony/rnoh/cli.js +55 -0
  49. package/build/src/harmony/rnoh/link.d.ts +2 -0
  50. package/build/src/harmony/rnoh/link.js +419 -0
  51. package/build/src/harmony/rnoh/packageMetadata.d.ts +12 -0
  52. package/build/src/harmony/rnoh/packageMetadata.js +125 -0
  53. package/build/src/harmony/transaction/previousState.d.ts +15 -0
  54. package/build/src/harmony/transaction/previousState.js +96 -0
  55. package/build/src/harmony/transaction/publish.d.ts +6 -0
  56. package/build/src/harmony/transaction/publish.js +272 -0
  57. package/build/src/harmony/transaction/stageProject.d.ts +18 -0
  58. package/build/src/harmony/transaction/stageProject.js +313 -0
  59. package/build/src/metadata/descriptor.d.ts +19 -0
  60. package/build/src/metadata/descriptor.js +306 -0
  61. package/build/src/metadata/host.d.ts +5 -0
  62. package/build/src/metadata/host.js +37 -0
  63. package/build/src/metadata/schema.d.ts +10 -0
  64. package/build/src/metadata/schema.js +171 -0
  65. package/build/src/types.d.ts +194 -0
  66. package/build/src/types.js +2 -0
  67. package/build/src/utilities/values.d.ts +17 -0
  68. package/build/src/utilities/values.js +205 -0
  69. package/harmony-module.schema.json +28 -0
  70. package/index.ts +41 -0
  71. package/package.json +66 -0
  72. package/src/autolinking/link.ts +170 -0
  73. package/src/autolinking/resolve.ts +33 -0
  74. package/src/autolinking/search.ts +369 -0
  75. package/src/autolinking/verify.ts +415 -0
  76. package/src/commands/autolinkingOptions.ts +38 -0
  77. package/src/commands/cli.ts +56 -0
  78. package/src/commands/link.ts +28 -0
  79. package/src/commands/output.ts +36 -0
  80. package/src/commands/resolve.ts +14 -0
  81. package/src/commands/search.ts +14 -0
  82. package/src/commands/verify.ts +17 -0
  83. package/src/config/constants.ts +45 -0
  84. package/src/config/options.ts +195 -0
  85. package/src/errors.ts +48 -0
  86. package/src/harmony/manifest/generate.ts +65 -0
  87. package/src/harmony/manifest/index.ts +81 -0
  88. package/src/harmony/materialize.ts +382 -0
  89. package/src/harmony/ohpm/dependencies.ts +59 -0
  90. package/src/harmony/persistence/canonicalize.ts +156 -0
  91. package/src/harmony/providers/host.ts +147 -0
  92. package/src/harmony/rnoh/cli.ts +59 -0
  93. package/src/harmony/rnoh/link.ts +481 -0
  94. package/src/harmony/rnoh/packageMetadata.ts +105 -0
  95. package/src/harmony/transaction/previousState.ts +71 -0
  96. package/src/harmony/transaction/publish.ts +287 -0
  97. package/src/harmony/transaction/stageProject.ts +345 -0
  98. package/src/metadata/descriptor.ts +324 -0
  99. package/src/metadata/host.ts +41 -0
  100. package/src/metadata/schema.ts +184 -0
  101. package/src/types.ts +220 -0
  102. package/src/utilities/values.ts +190 -0
  103. package/tsconfig.json +24 -0
@@ -0,0 +1,105 @@
1
+ import * as path from 'node:path';
2
+
3
+ import Case from 'case';
4
+ import { HarmonyAutolinkingError } from '../../errors';
5
+
6
+ function packageNameParts(packageName) {
7
+ if (packageName.startsWith('@')) {
8
+ const [scopeName, name] = packageName.slice(1).split('/');
9
+ return { scopeName, name };
10
+ }
11
+
12
+ return { scopeName: null, name: packageName };
13
+ }
14
+
15
+ function defaultPackageClassName(packageName) {
16
+ const { scopeName, name } = packageNameParts(packageName);
17
+ return scopeName
18
+ ? `${Case.pascal(scopeName)}${Case.pascal(name)}Package`
19
+ : `${Case.pascal(name)}Package`;
20
+ }
21
+
22
+ function defaultCmakeTargetForNpmPackage(packageName) {
23
+ const { scopeName, name } = packageNameParts(packageName);
24
+ return scopeName
25
+ ? `rnoh__${Case.snake(scopeName)}__${Case.snake(name)}`
26
+ : `rnoh__${Case.snake(name)}`;
27
+ }
28
+
29
+ function defaultOhPackageNameForNpmPackage(packageName) {
30
+ const { scopeName, name } = packageNameParts(packageName);
31
+ return scopeName
32
+ ? `@rnoh/${Case.kebab(scopeName)}--${Case.kebab(name)}`
33
+ : `@rnoh/${Case.kebab(name)}`;
34
+ }
35
+
36
+ function ohPackageNameForHar(base, harPath, harCount) {
37
+ return harCount > 1
38
+ ? `${base}--${path.basename(harPath, '.har')}`
39
+ : base;
40
+ }
41
+
42
+ function resolveHarPackageMappings(descriptor) {
43
+ const harPaths = descriptor.rnoh.harPaths || [];
44
+ if (harPaths.length === 0) return [];
45
+
46
+ const configured = descriptor.rnoh.ohPackageName;
47
+ if (configured === undefined) {
48
+ const base = defaultOhPackageNameForNpmPackage(descriptor.packageName);
49
+ return harPaths.map(harPath => ({
50
+ harPath,
51
+ ohPackageName: ohPackageNameForHar(base, harPath, harPaths.length),
52
+ }));
53
+ }
54
+
55
+ if (typeof configured === 'string') {
56
+ return harPaths.map(harPath => ({
57
+ harPath,
58
+ ohPackageName: ohPackageNameForHar(configured, harPath, harPaths.length),
59
+ }));
60
+ }
61
+
62
+ const defaultName = defaultOhPackageNameForNpmPackage(descriptor.packageName);
63
+ return harPaths.map((harPath) => {
64
+ const mapping = configured.find(item => item.harName === path.basename(harPath));
65
+ if (!mapping) return {
66
+ harPath,
67
+ ohPackageName: ohPackageNameForHar(defaultName, harPath, harPaths.length),
68
+ };
69
+ return {
70
+ harPath,
71
+ ohPackageName: mapping.packageName,
72
+ ...(mapping.version ? { version: mapping.version } : {}),
73
+ };
74
+ });
75
+ }
76
+
77
+ function resolveRnohMetadata(descriptor) {
78
+ const pkg = defaultPackageClassName(descriptor.packageName);
79
+ if ((descriptor.rnoh.etsPackageClassName === undefined
80
+ || descriptor.rnoh.cppPackageClassName === undefined)
81
+ && !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(pkg)) {
82
+ throw new HarmonyAutolinkingError(
83
+ 'INVALID_METADATA',
84
+ 'The npm package name cannot produce a valid default RNOH package class; configure the package class names explicitly.',
85
+ { packageName: descriptor.packageName, stage: 'metadata' }
86
+ );
87
+ }
88
+
89
+ return {
90
+ packageName: descriptor.packageName,
91
+ harMappings: resolveHarPackageMappings(descriptor),
92
+ etsPackageClassName: descriptor.rnoh.etsPackageClassName || pkg,
93
+ etsPackageImport: descriptor.rnoh.etsPackageImport || 'default',
94
+ cppPackageClassName: descriptor.rnoh.cppPackageClassName || pkg,
95
+ cmakeLibraryTargetName: descriptor.rnoh.cmakeLibraryTargetName
96
+ || defaultCmakeTargetForNpmPackage(descriptor.packageName),
97
+ };
98
+ }
99
+
100
+ export {
101
+ defaultCmakeTargetForNpmPackage,
102
+ defaultOhPackageNameForNpmPackage,
103
+ ohPackageNameForHar,
104
+ resolveRnohMetadata,
105
+ };
@@ -0,0 +1,71 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ import { ExpoArtifacts, ManifestArtifact, RnohArtifacts } from '../../config/constants';
5
+ import { ohpmDependenciesFromManifest, validateManifest } from '../manifest';
6
+ import { pathExistsAsync } from '../../utilities/values';
7
+
8
+ const ManagedSuffixes = [
9
+ ...Object.values(RnohArtifacts),
10
+ ...Object.values(ExpoArtifacts),
11
+ ];
12
+
13
+ function isManagedArtifactPath(relativePath) {
14
+ if (relativePath === ManifestArtifact) return true;
15
+ return ManagedSuffixes.some(suffix => relativePath === suffix || relativePath.endsWith(`/${suffix}`));
16
+ }
17
+
18
+ function normalizeManagedArtifactPath(value) {
19
+ if (typeof value !== 'string' || !value || value.includes('\\') || path.posix.isAbsolute(value)) {
20
+ return null;
21
+ }
22
+
23
+ const normalized = path.posix.normalize(value);
24
+ if (normalized !== value || normalized === '..' || normalized.startsWith('../')) return null;
25
+ return isManagedArtifactPath(normalized) ? normalized : null;
26
+ }
27
+
28
+ function staleManifestWarning(message) {
29
+ return {
30
+ severity: 'warning',
31
+ code: 'ERR_EXPO_HARMONY_STALE_MANIFEST_IGNORED',
32
+ message,
33
+ stage: 'publish',
34
+ };
35
+ }
36
+
37
+ async function readPreviousAutolinkingStateAsync(projectRoot) {
38
+ const manifestPath = path.join(projectRoot, ManifestArtifact);
39
+
40
+ if (!(await pathExistsAsync(manifestPath))) {
41
+ return { artifacts: [], managedOhpmPackageNames: [], warning: null };
42
+ }
43
+ try {
44
+ const stat = await fs.promises.lstat(manifestPath);
45
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new TypeError('manifest is not a regular file');
46
+ const manifest = validateManifest(
47
+ JSON.parse(await fs.promises.readFile(manifestPath, 'utf8')),
48
+ { file: manifestPath }
49
+ );
50
+ const normalized: Array<string | null> = manifest.managedArtifacts.map(normalizeManagedArtifactPath);
51
+ if (normalized.some(value => value == null)) throw new TypeError('manifest contains an unsafe path');
52
+ const artifacts = normalized.filter((value): value is string => value !== null);
53
+
54
+ return {
55
+ artifacts: [...new Set(artifacts)].map(relative => path.join(projectRoot, ...relative.split('/'))),
56
+ managedOhpmPackageNames: Object.keys(ohpmDependenciesFromManifest(manifest))
57
+ .sort((left, right) => left.localeCompare(right, 'en')),
58
+ warning: null,
59
+ };
60
+ } catch (_cause) {
61
+ return {
62
+ artifacts: [],
63
+ managedOhpmPackageNames: [],
64
+ warning: staleManifestWarning('The previous Harmony autolinking manifest is invalid; stale artifact cleanup was skipped.'),
65
+ };
66
+ }
67
+ }
68
+
69
+ export {
70
+ readPreviousAutolinkingStateAsync,
71
+ };
@@ -0,0 +1,287 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { HarmonyAutolinkingError } from '../../errors';
5
+ import { isPathInside, pathExistsAsync } from '../../utilities/values';
6
+
7
+ const MALFORMED_LOCK_GRACE_MS = 30_000;
8
+
9
+ async function processIsAliveAsync(pid) {
10
+ if (!Number.isInteger(pid) || pid <= 0) return false;
11
+
12
+ try {
13
+ process.kill(pid, 0);
14
+ return true;
15
+ } catch (cause) {
16
+ return cause.code !== 'ESRCH';
17
+ }
18
+ }
19
+
20
+ async function removeStaleLockAsync(lockPath) {
21
+ let observed;
22
+ try {
23
+ observed = await fs.promises.lstat(lockPath);
24
+ if (!observed.isFile() || observed.isSymbolicLink()) return false;
25
+ } catch (_cause) {
26
+ return false;
27
+ }
28
+
29
+ let contents;
30
+ try {
31
+ contents = JSON.parse(await fs.promises.readFile(lockPath, 'utf8'));
32
+ } catch (_cause) {
33
+ if (Date.now() - observed.mtimeMs < MALFORMED_LOCK_GRACE_MS) return false;
34
+ contents = null;
35
+ }
36
+
37
+ if (contents && await processIsAliveAsync(contents.pid)) return false;
38
+ try {
39
+ const current = await fs.promises.lstat(lockPath);
40
+ if (current.dev !== observed.dev || current.ino !== observed.ino
41
+ || current.mtimeMs !== observed.mtimeMs || current.size !== observed.size) {
42
+ return false;
43
+ }
44
+ await fs.promises.unlink(lockPath);
45
+ return true;
46
+ } catch (_cause) {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ async function releasePublishLockAsync(handle, lockPath) {
52
+ let owned;
53
+ try {
54
+ owned = await handle.stat();
55
+ } catch (_cause) {
56
+ // Closing the descriptor is still required even if its metadata is unavailable.
57
+ }
58
+
59
+ await handle.close().catch(() => {});
60
+ if (!owned) return false;
61
+
62
+ try {
63
+ const current = await fs.promises.lstat(lockPath);
64
+ if (current.dev !== owned.dev || current.ino !== owned.ino) return false;
65
+ await fs.promises.unlink(lockPath);
66
+ return true;
67
+ } catch (_cause) {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ async function acquirePublishLockAsync(lockPath) {
73
+ await fs.promises.mkdir(path.dirname(lockPath), { recursive: true });
74
+
75
+ for (let attempt = 0; attempt < 2; attempt += 1) {
76
+ try {
77
+ const handle = await fs.promises.open(lockPath, 'wx', 0o600);
78
+
79
+ try {
80
+ await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
81
+ await handle.sync();
82
+ return handle;
83
+ } catch (cause) {
84
+ await releasePublishLockAsync(handle, lockPath);
85
+ throw new HarmonyAutolinkingError(
86
+ 'PUBLISH_FAILED',
87
+ 'Unable to initialize the Harmony autolinking publish lock.',
88
+ { cause, stage: 'publish' }
89
+ );
90
+ }
91
+ } catch (cause) {
92
+ if (cause.code === 'EEXIST' && attempt === 0 && await removeStaleLockAsync(lockPath)) continue;
93
+ if (cause.code === 'EEXIST') {
94
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', 'Another Harmony autolinking publish is in progress.', { cause, stage: 'publish' });
95
+ }
96
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', 'Unable to acquire the Harmony autolinking lock.', { cause, stage: 'publish' });
97
+ }
98
+ }
99
+
100
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', 'Unable to acquire the Harmony autolinking lock.', { stage: 'publish' });
101
+ }
102
+
103
+ function assertLexicallyContained(target, allowedRoot) {
104
+ const resolved = path.resolve(target);
105
+ const absoluteRoot = path.resolve(allowedRoot);
106
+
107
+ if (!isPathInside(absoluteRoot, resolved)) {
108
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', `Publish target escapes the allowed root: ${resolved}`, { stage: 'publish' });
109
+ }
110
+
111
+ if (resolved.split(path.sep).includes('node_modules')) {
112
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', 'Publishing into node_modules is forbidden.', { stage: 'publish' });
113
+ }
114
+ return resolved;
115
+ }
116
+
117
+ async function assertSafeTargetAsync(target, allowedRoot) {
118
+ const resolved = assertLexicallyContained(target, allowedRoot);
119
+ const absoluteRoot = path.resolve(allowedRoot);
120
+
121
+ if (await pathExistsAsync(resolved)) {
122
+ const stat = await fs.promises.lstat(resolved);
123
+ if (stat.isSymbolicLink()) {
124
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', `Publish target must not be a symbolic link: ${resolved}`, { stage: 'publish' });
125
+ }
126
+ }
127
+
128
+ if (await pathExistsAsync(absoluteRoot)) {
129
+ const realRoot = await fs.promises.realpath(absoluteRoot);
130
+ let ancestor = path.dirname(resolved);
131
+ while (!(await pathExistsAsync(ancestor))) {
132
+ const parent = path.dirname(ancestor);
133
+ if (parent === ancestor) break;
134
+ ancestor = parent;
135
+ }
136
+ const realAncestor = await fs.promises.realpath(ancestor);
137
+ if (!isPathInside(realRoot, realAncestor)) {
138
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', `Publish target resolves outside the allowed root: ${resolved}`, { stage: 'publish' });
139
+ }
140
+ }
141
+
142
+ return resolved;
143
+ }
144
+
145
+ async function stageFileAsync(file, stagedTarget) {
146
+ if ((file.source === undefined) === (file.content === undefined)) {
147
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'Each publish file needs exactly one source or content.', { stage: 'publish' });
148
+ }
149
+
150
+ if (file.source !== undefined) {
151
+ await fs.promises.copyFile(file.source, stagedTarget);
152
+ } else {
153
+ if (typeof file.content !== 'string' && !Buffer.isBuffer(file.content)) {
154
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'Publish file content must be a string or Buffer.', { stage: 'publish' });
155
+ }
156
+ await fs.promises.writeFile(stagedTarget, file.content, file.mode ? { mode: file.mode } : undefined);
157
+ }
158
+ }
159
+
160
+ async function filesEqualAsync(left, right) {
161
+ if (!(await pathExistsAsync(left)) || !(await pathExistsAsync(right))) return false;
162
+
163
+ const [leftBuffer, rightBuffer] = await Promise.all([
164
+ fs.promises.readFile(left),
165
+ fs.promises.readFile(right),
166
+ ]);
167
+ return leftBuffer.equals(rightBuffer as unknown as Uint8Array);
168
+ }
169
+
170
+ async function removeEmptyParentsAsync(directory, stopAt) {
171
+ let current = directory;
172
+
173
+ while (isPathInside(stopAt, current) && current !== stopAt) {
174
+ try {
175
+ await fs.promises.rmdir(current);
176
+ } catch (_cause) {
177
+ break;
178
+ }
179
+ current = path.dirname(current);
180
+ }
181
+ }
182
+
183
+ async function publishArtifactsAsync(options) {
184
+ if (!Array.isArray(options?.files) || options.files.length === 0) {
185
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'A publish transaction requires at least one file.', { stage: 'publish' });
186
+ }
187
+
188
+ if (typeof options.allowedRoot !== 'string' || !options.allowedRoot) {
189
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'A publish transaction requires allowedRoot.', { stage: 'publish' });
190
+ }
191
+
192
+ const allowedRoot = path.resolve(options.allowedRoot);
193
+ const files = [];
194
+
195
+ for (const file of options.files) {
196
+ if (!file || typeof file.target !== 'string' || !file.target) {
197
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'Publish file target must be a non-empty string.', { stage: 'publish' });
198
+ }
199
+ files.push({ ...file, target: await assertSafeTargetAsync(file.target, allowedRoot) });
200
+ }
201
+
202
+ if (new Set(files.map(file => file.target)).size !== files.length) {
203
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', 'A publish transaction contains duplicate targets.', { stage: 'publish' });
204
+ }
205
+
206
+ const stale = [];
207
+ for (const target of options.stale || []) {
208
+ const safeTarget = await assertSafeTargetAsync(target, allowedRoot);
209
+ if (!files.some(file => file.target === safeTarget) && !stale.includes(safeTarget)) stale.push(safeTarget);
210
+ }
211
+
212
+ const lockPath = await assertSafeTargetAsync(options.lockPath || path.join(allowedRoot, '.expo-harmony-autolinking.lock'), allowedRoot);
213
+ await fs.promises.mkdir(path.dirname(lockPath), { recursive: true });
214
+
215
+ const tmp = await fs.promises.mkdtemp(path.join(path.dirname(lockPath), '.expo-harmony-publish-'));
216
+ const prepared = [];
217
+ let lockHandle;
218
+ const touched = [];
219
+ const changed = [];
220
+ const unchanged = [];
221
+
222
+ try {
223
+ for (let index = 0; index < files.length; index += 1) {
224
+ const staged = path.join(tmp, `new-${index}`);
225
+ await stageFileAsync(files[index], staged);
226
+ prepared.push({ ...files[index], staged });
227
+ }
228
+
229
+ lockHandle = await acquirePublishLockAsync(lockPath);
230
+ for (let index = 0; index < prepared.length; index += 1) {
231
+ const file = prepared[index];
232
+ if (await filesEqualAsync(file.staged, file.target)) {
233
+ unchanged.push(file.target);
234
+ continue;
235
+ }
236
+ const existed = await pathExistsAsync(file.target);
237
+ const backup = existed ? path.join(tmp, `backup-${index}`) : null;
238
+ if (backup) await fs.promises.copyFile(file.target, backup);
239
+ touched.push({ target: file.target, existed, backup });
240
+ await fs.promises.mkdir(path.dirname(file.target), { recursive: true });
241
+ await fs.promises.rename(file.staged, file.target);
242
+ if (file.mode) await fs.promises.chmod(file.target, file.mode);
243
+ changed.push(file.target);
244
+ }
245
+
246
+ for (let index = 0; index < stale.length; index += 1) {
247
+ const target = stale[index];
248
+ if (!(await pathExistsAsync(target))) continue;
249
+ const backup = path.join(tmp, `stale-${index}`);
250
+ await fs.promises.copyFile(target, backup);
251
+ touched.push({ target, existed: true, backup });
252
+ await fs.promises.unlink(target);
253
+ changed.push(target);
254
+ }
255
+
256
+ return { changed, unchanged, warnings: [] };
257
+ } catch (cause) {
258
+ const failures = [];
259
+
260
+ for (const entry of [...touched].reverse()) {
261
+ try {
262
+ if (entry.existed) {
263
+ await fs.promises.mkdir(path.dirname(entry.target), { recursive: true });
264
+ await fs.promises.copyFile(entry.backup, entry.target);
265
+ } else if (await pathExistsAsync(entry.target)) {
266
+ await fs.promises.unlink(entry.target);
267
+ await removeEmptyParentsAsync(path.dirname(entry.target), allowedRoot);
268
+ }
269
+ } catch (error) {
270
+ failures.push({ target: entry.target, message: error.message });
271
+ }
272
+ }
273
+
274
+ throw new HarmonyAutolinkingError('PUBLISH_FAILED', 'Harmony autolinking publish failed and was rolled back.', {
275
+ cause,
276
+ details: failures.length > 0 ? { rollbackFailures: failures } : undefined,
277
+ stage: 'publish',
278
+ });
279
+ } finally {
280
+ if (lockHandle) await releasePublishLockAsync(lockHandle, lockPath);
281
+ await fs.promises.rm(tmp, { recursive: true, force: true }).catch(() => {});
282
+ }
283
+ }
284
+
285
+ export {
286
+ publishArtifactsAsync,
287
+ };