@platforma-sdk/block-tools 2.5.92 → 2.6.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 (36) hide show
  1. package/README.md +38 -2
  2. package/dist/cli.js +5 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cli.mjs +208 -129
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/cmd/index.d.ts +14 -10
  7. package/dist/cmd/list-overview-snapshots.d.ts +9 -0
  8. package/dist/cmd/restore-overview-from-snapshot.d.ts +10 -0
  9. package/dist/config-DjpRXRy9.js +3 -0
  10. package/dist/config-DjpRXRy9.js.map +1 -0
  11. package/dist/config-XBQ2O39y.mjs +2020 -0
  12. package/dist/config-XBQ2O39y.mjs.map +1 -0
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +2 -2
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/registry_v1/config_schema.d.ts +5 -5
  18. package/dist/v2/model/block_description.d.ts +7329 -1422
  19. package/dist/v2/model/block_meta.d.ts +481 -88
  20. package/dist/v2/registry/registry.d.ts +16 -1
  21. package/dist/v2/registry/registry_reader.d.ts +2 -3
  22. package/dist/v2/registry/schema_internal.d.ts +7 -1
  23. package/package.json +7 -7
  24. package/src/cmd/index.ts +14 -10
  25. package/src/cmd/list-overview-snapshots.ts +46 -0
  26. package/src/cmd/restore-overview-from-snapshot.ts +78 -0
  27. package/src/v2/registry/registry.test.ts +176 -4
  28. package/src/v2/registry/registry.ts +146 -13
  29. package/src/v2/registry/registry_reader.ts +5 -6
  30. package/src/v2/registry/schema_internal.ts +16 -1
  31. package/src/v2/registry/schema_public.ts +5 -5
  32. package/dist/config-VnABe7ki.mjs +0 -1951
  33. package/dist/config-VnABe7ki.mjs.map +0 -1
  34. package/dist/config-t7F2nAAr.js +0 -3
  35. package/dist/config-t7F2nAAr.js.map +0 -1
  36. package/dist/v2/registry/schema_public.d.ts +0 -18475
@@ -3,11 +3,24 @@ import { RegistryStorage } from '../../io/storage';
3
3
  import { BlockPackId, BlockPackIdNoVersion, BlockPackManifest } from '@milaboratories/pl-model-middle-layer';
4
4
  import { GlobalOverviewReg, PackageOverview } from './schema_public';
5
5
  import { RelativeContentReader } from '../model';
6
+ export interface BlockRegistrySettings {
7
+ skipSnapshotCreation?: boolean;
8
+ }
9
+ export interface GlobalOverviewBackupDescription {
10
+ timestamp: string;
11
+ path: string;
12
+ }
6
13
  export declare class BlockRegistryV2 {
7
14
  private readonly storage;
8
15
  private readonly logger;
16
+ private readonly settings;
9
17
  private readonly gzipAsync;
10
- constructor(storage: RegistryStorage, logger?: MiLogger);
18
+ private readonly gunzipAsync;
19
+ constructor(storage: RegistryStorage, logger?: MiLogger, settings?: BlockRegistrySettings);
20
+ private generateTimestamp;
21
+ private generatePreWriteTimestamp;
22
+ private createGlobalOverviewSnapshot;
23
+ private createPackageOverviewSnapshot;
11
24
  private updateRegistry;
12
25
  updateIfNeeded(mode?: 'force' | 'normal' | 'dry-run'): Promise<void>;
13
26
  getPackageOverview(name: BlockPackIdNoVersion): Promise<undefined | PackageOverview>;
@@ -15,5 +28,7 @@ export declare class BlockRegistryV2 {
15
28
  private marchChanged;
16
29
  addPackageToChannel(id: BlockPackId, channel: string): Promise<void>;
17
30
  removePackageFromChannel(id: BlockPackId, channel: string): Promise<void>;
31
+ listGlobalOverviewSnapshots(): Promise<GlobalOverviewBackupDescription[]>;
32
+ restoreGlobalOverviewFromSnapshot(backupId: string): Promise<void>;
18
33
  publishPackage(manifest: BlockPackManifest, fileReader: RelativeContentReader): Promise<void>;
19
34
  }
@@ -1,7 +1,6 @@
1
- import { BlockPackId, BlockPackIdNoVersion, BlockPackOverview, UpdateSuggestions, SingleBlockPackOverview } from '@milaboratories/pl-model-middle-layer';
1
+ import { BlockPackId, BlockPackIdNoVersion, UpdateSuggestions, SingleBlockPackOverview, BlockPackOverviewNoRegistryId } from '@milaboratories/pl-model-middle-layer';
2
2
  import { FolderReader } from '../../io';
3
3
  import { BlockComponentsAbsoluteUrl } from '../model';
4
- export type BlockPackOverviewNoRegLabel = Omit<BlockPackOverview, 'registryId'>;
5
4
  export type RegistryV2ReaderOps = {
6
5
  /** Number of milliseconds to cache retrieved block list for */
7
6
  cacheBlockListFor: number;
@@ -24,7 +23,7 @@ export declare class RegistryV2Reader {
24
23
  private embedMetaContent;
25
24
  private listCacheTimestamp;
26
25
  private listCache;
27
- listBlockPacks(): Promise<BlockPackOverviewNoRegLabel[]>;
26
+ listBlockPacks(): Promise<BlockPackOverviewNoRegistryId[]>;
28
27
  getLatestOverview(id: BlockPackIdNoVersion, channel: string): Promise<SingleBlockPackOverview | undefined>;
29
28
  getUpdateSuggestions(id: BlockPackId, channel: string): Promise<UpdateSuggestions<string> | undefined>;
30
29
  getSpecificOverview(id: BlockPackId, channel: string): Promise<SingleBlockPackOverview>;
@@ -1,6 +1,12 @@
1
- import { BlockPackId } from '@milaboratories/pl-model-middle-layer';
1
+ import { BlockPackId, BlockPackIdNoVersion } from '@milaboratories/pl-model-middle-layer';
2
2
  export declare const VersionUpdatesPrefix = "_updates_v2/per_package_version/";
3
3
  export declare function packageUpdateSeedPath(bp: BlockPackId, seed: string): string;
4
4
  export declare const PackageUpdatePattern: RegExp;
5
5
  export declare const GlobalUpdateSeedInFile = "_updates_v2/_global_update_in";
6
6
  export declare const GlobalUpdateSeedOutFile = "_updates_v2/_global_update_out";
7
+ export declare const OverviewSnapshotsPrefix = "_overview_snapshots_v2/";
8
+ export declare const GlobalSnapshotsPrefix = "_overview_snapshots_v2/global/";
9
+ export declare const PackageSnapshotsPrefix = "_overview_snapshots_v2/per_package/";
10
+ export declare const GlobalOverviewSnapshotPattern: RegExp;
11
+ export declare function globalOverviewSnapshotPath(timestamp: string): string;
12
+ export declare function packageOverviewSnapshotPath(bp: BlockPackIdNoVersion, timestamp: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/block-tools",
3
- "version": "2.5.92",
3
+ "version": "2.6.1",
4
4
  "description": "Utility to manipulate Platforma Blocks and Block Registry",
5
5
  "types": "./dist/lib.d.ts",
6
6
  "main": "./dist/index.js",
@@ -33,12 +33,12 @@
33
33
  "tar": "^7.4.3",
34
34
  "yaml": "^2.8.0",
35
35
  "zod": "~3.23.8",
36
- "@milaboratories/pl-model-common": "1.19.14",
37
- "@milaboratories/pl-model-middle-layer": "1.8.21",
38
- "@milaboratories/resolve-helper": "1.1.1",
39
36
  "@milaboratories/pl-http": "1.1.7",
40
- "@milaboratories/ts-helpers-oclif": "1.1.27",
41
- "@milaboratories/ts-helpers": "1.4.6"
37
+ "@milaboratories/pl-model-common": "1.19.16",
38
+ "@milaboratories/resolve-helper": "1.1.1",
39
+ "@milaboratories/ts-helpers-oclif": "1.1.28",
40
+ "@milaboratories/pl-model-middle-layer": "1.8.23",
41
+ "@milaboratories/ts-helpers": "1.4.7"
42
42
  },
43
43
  "devDependencies": {
44
44
  "typescript": "~5.6.3",
@@ -55,8 +55,8 @@
55
55
  "@jest/globals": "^29.7.0",
56
56
  "oclif": "^4.16.2",
57
57
  "ts-jest": "^29.2.6",
58
- "@milaboratories/build-configs": "1.0.8",
59
58
  "@milaboratories/oclif-index": "1.1.1",
59
+ "@milaboratories/build-configs": "1.0.8",
60
60
  "@milaboratories/ts-configs": "1.0.6"
61
61
  },
62
62
  "oclif": {
package/src/cmd/index.ts CHANGED
@@ -2,19 +2,23 @@
2
2
 
3
3
  import Cmd0 from './build-meta';
4
4
  import Cmd1 from './build-model';
5
- import Cmd3 from './mark-stable';
6
- import Cmd4 from './pack';
7
- import Cmd5 from './publish';
8
- import Cmd6 from './refresh-registry';
9
- import Cmd7 from './upload-package-v1';
5
+ import Cmd3 from './list-overview-snapshots';
6
+ import Cmd4 from './mark-stable';
7
+ import Cmd5 from './pack';
8
+ import Cmd6 from './publish';
9
+ import Cmd7 from './refresh-registry';
10
+ import Cmd8 from './restore-overview-from-snapshot';
11
+ import Cmd9 from './upload-package-v1';
10
12
 
11
13
  // prettier-ignore
12
14
  export const COMMANDS = {
13
15
  'build-meta': Cmd0,
14
16
  'build-model': Cmd1,
15
- 'mark-stable': Cmd3,
16
- 'pack': Cmd4,
17
- 'publish': Cmd5,
18
- 'refresh-registry': Cmd6,
19
- 'upload-package-v1': Cmd7
17
+ 'list-overview-snapshots': Cmd3,
18
+ 'mark-stable': Cmd4,
19
+ 'pack': Cmd5,
20
+ 'publish': Cmd6,
21
+ 'refresh-registry': Cmd7,
22
+ 'restore-overview-from-snapshot': Cmd8,
23
+ 'upload-package-v1': Cmd9,
20
24
  };
@@ -0,0 +1,46 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { BlockRegistryV2 } from '../v2/registry/registry';
3
+ import { storageByUrl } from '../io/storage';
4
+ import { OclifLoggerAdapter } from '@milaboratories/ts-helpers-oclif';
5
+
6
+ export default class ListOverviewSnapshots extends Command {
7
+ static description = 'List all available global overview snapshots in the registry';
8
+
9
+ static flags = {
10
+ registry: Flags.string({
11
+ char: 'r',
12
+ summary: 'full address of the registry',
13
+ helpValue: '<address>',
14
+ env: 'PL_REGISTRY',
15
+ required: true
16
+ }),
17
+
18
+ json: Flags.boolean({
19
+ summary: 'output in JSON format',
20
+ default: false
21
+ })
22
+ };
23
+
24
+ public async run(): Promise<void> {
25
+ const { flags } = await this.parse(ListOverviewSnapshots);
26
+ const storage = storageByUrl(flags.registry);
27
+ const registry = new BlockRegistryV2(storage, new OclifLoggerAdapter(this));
28
+
29
+ const snapshots = await registry.listGlobalOverviewSnapshots();
30
+
31
+ if (flags.json) {
32
+ this.log(JSON.stringify(snapshots, null, 2));
33
+ } else {
34
+ if (snapshots.length === 0) {
35
+ this.log('No snapshots found.');
36
+ } else {
37
+ this.log(`Found ${snapshots.length} snapshot(s):\n`);
38
+ for (const snapshot of snapshots) {
39
+ this.log(` ${snapshot.timestamp}`);
40
+ this.log(` Path: ${snapshot.path}`);
41
+ this.log('');
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,78 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { BlockRegistryV2 } from '../v2/registry/registry';
3
+ import { storageByUrl } from '../io/storage';
4
+ import { OclifLoggerAdapter } from '@milaboratories/ts-helpers-oclif';
5
+
6
+ export default class RestoreOverviewFromSnapshot extends Command {
7
+ static description = 'Restore global overview from a snapshot';
8
+
9
+ static flags = {
10
+ registry: Flags.string({
11
+ char: 'r',
12
+ summary: 'full address of the registry',
13
+ helpValue: '<address>',
14
+ env: 'PL_REGISTRY',
15
+ required: true
16
+ }),
17
+
18
+ snapshot: Flags.string({
19
+ char: 's',
20
+ summary: 'snapshot timestamp ID to restore from',
21
+ helpValue: '<timestamp>',
22
+ required: true
23
+ }),
24
+
25
+ 'skip-confirmation': Flags.boolean({
26
+ summary: 'skip confirmation prompt (use with caution)',
27
+ default: false
28
+ })
29
+ };
30
+
31
+ public async run(): Promise<void> {
32
+ const { flags } = await this.parse(RestoreOverviewFromSnapshot);
33
+ const storage = storageByUrl(flags.registry);
34
+ const registry = new BlockRegistryV2(storage, new OclifLoggerAdapter(this));
35
+
36
+ // Check if snapshot exists
37
+ const snapshots = await registry.listGlobalOverviewSnapshots();
38
+ const targetSnapshot = snapshots.find(s => s.timestamp === flags.snapshot);
39
+
40
+ if (!targetSnapshot) {
41
+ this.error(`Snapshot '${flags.snapshot}' not found. Available snapshots:\n${
42
+ snapshots.map(s => ` - ${s.timestamp}`).join('\n') || ' (none)'
43
+ }`);
44
+ }
45
+
46
+ // Confirmation prompt (unless skipped)
47
+ if (!flags['skip-confirmation']) {
48
+ const readline = await import('node:readline');
49
+ const rl = readline.createInterface({
50
+ input: process.stdin,
51
+ output: process.stdout
52
+ });
53
+
54
+ const answer = await new Promise<string>((resolve) => {
55
+ rl.question(
56
+ `⚠️ This will overwrite the current global overview with snapshot '${flags.snapshot}'.\n` +
57
+ `Are you sure you want to continue? (y/N): `,
58
+ resolve
59
+ );
60
+ });
61
+
62
+ rl.close();
63
+
64
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
65
+ this.log('Restore cancelled.');
66
+ return;
67
+ }
68
+ }
69
+
70
+ // Perform restore
71
+ try {
72
+ await registry.restoreGlobalOverviewFromSnapshot(flags.snapshot);
73
+ this.log(`✅ Successfully restored global overview from snapshot '${flags.snapshot}'`);
74
+ } catch (error) {
75
+ this.error(`Failed to restore from snapshot: ${error}`);
76
+ }
77
+ }
78
+ }
@@ -5,8 +5,9 @@ import path from 'path';
5
5
  import fsp from 'fs/promises';
6
6
  import { BlockRegistryV2 } from './registry';
7
7
  import semver from 'semver';
8
- import { UpdateSuggestions } from '@milaboratories/pl-model-middle-layer';
8
+ import { UpdateSuggestions, BlockPackManifest } from '@milaboratories/pl-model-middle-layer';
9
9
  import { inferUpdateSuggestions } from './registry_reader';
10
+ import { OverviewSnapshotsPrefix } from './schema_internal';
10
11
 
11
12
  type TestStorageInstance = {
12
13
  storage: RegistryStorage;
@@ -55,11 +56,182 @@ if (testS3Address !== undefined) {
55
56
  });
56
57
  }
57
58
 
58
- test.each(testStorages)('full registry test with $name', async ({ storageProvider }) => {
59
+ test.each(testStorages)('registry snapshots test with $name', async ({ storageProvider }) => {
59
60
  const { storage, teardown } = storageProvider();
60
61
  const registry = new BlockRegistryV2(storage);
61
- registry.updateIfNeeded;
62
- await teardown();
62
+
63
+ try {
64
+ // Force an update to trigger snapshot creation (even with empty registry)
65
+ await registry.updateIfNeeded('force');
66
+
67
+ // Check that snapshot files actually exist in storage
68
+ const snapshotFiles = await storage.listFiles(OverviewSnapshotsPrefix);
69
+ expect(snapshotFiles.length).toBeGreaterThan(0);
70
+
71
+ // Check that snapshots were created
72
+ const snapshots = await registry.listGlobalOverviewSnapshots();
73
+ expect(snapshots.length).toBeGreaterThan(0);
74
+ expect(snapshots[0]).toHaveProperty('timestamp');
75
+ expect(snapshots[0]).toHaveProperty('path');
76
+ expect(snapshots[0].path).toMatch(/^_overview_snapshots_v2\/global\/.*\.json\.gz$/);
77
+
78
+ // Test that global overview files exist (should be empty initially)
79
+ const globalOverview = await registry.getGlobalOverview();
80
+ expect(globalOverview).toBeDefined();
81
+ expect(globalOverview?.packages).toHaveLength(0);
82
+
83
+ // Test restore functionality
84
+ const snapshotId = snapshots[0].timestamp;
85
+ await registry.restoreGlobalOverviewFromSnapshot(snapshotId);
86
+
87
+ // Verify restored overview is still valid
88
+ const restoredOverview = await registry.getGlobalOverview();
89
+ expect(restoredOverview).toBeDefined();
90
+ expect(restoredOverview?.packages).toHaveLength(0);
91
+
92
+ } finally {
93
+ await teardown();
94
+ }
95
+ });
96
+
97
+ test.each(testStorages)('registry snapshots disabled test with $name', async ({ storageProvider }) => {
98
+ const { storage, teardown } = storageProvider();
99
+ const registry = new BlockRegistryV2(storage, undefined, { skipSnapshotCreation: true });
100
+
101
+ try {
102
+ // Force an update which would normally create snapshots
103
+ await registry.updateIfNeeded('force');
104
+
105
+ // Check that no snapshots were created
106
+ const snapshots = await registry.listGlobalOverviewSnapshots();
107
+ expect(snapshots).toHaveLength(0);
108
+
109
+ const snapshotFiles = await storage.listFiles(OverviewSnapshotsPrefix);
110
+ expect(snapshotFiles).toHaveLength(0);
111
+
112
+ } finally {
113
+ await teardown();
114
+ }
115
+ });
116
+
117
+ test.each(testStorages)('force mode removes deleted packages and versions with $name', async ({ storageProvider }) => {
118
+ const { storage, teardown } = storageProvider();
119
+ const registry = new BlockRegistryV2(storage);
120
+
121
+ try {
122
+ // Create mock manifests for testing
123
+ const createMockManifest = (org: string, name: string, version: string): BlockPackManifest => ({
124
+ schema: 'v2',
125
+ description: {
126
+ id: { organization: org, name: name, version: version },
127
+ title: `Test ${name}`,
128
+ summary: 'Test package',
129
+ components: {
130
+ workflow: { type: 'workflow-v1', main: { type: 'relative', path: 'workflow.json' } },
131
+ model: { type: 'relative', path: 'model.json' },
132
+ ui: { type: 'relative', path: 'ui.json' }
133
+ },
134
+ meta: {
135
+ title: `Test ${name}`,
136
+ description: 'Test package description',
137
+ organization: {
138
+ name: 'Test Organization',
139
+ url: 'https://test.com'
140
+ },
141
+ tags: []
142
+ }
143
+ },
144
+ files: [
145
+ {
146
+ name: 'model.json',
147
+ size: 13,
148
+ sha256: '6FD977DB9B2AFE87A9CEEE48432881299A6AAF83D935FBBE83007660287F9C2E'
149
+ }
150
+ ]
151
+ });
152
+
153
+ const mockFileReader = async (fileName: string) => {
154
+ if (fileName === 'model.json') {
155
+ return Buffer.from('{"test":true}');
156
+ }
157
+ throw new Error(`Unknown file: ${fileName}`);
158
+ };
159
+
160
+ // 1. Publish multiple packages and versions
161
+ const pkg1v1 = createMockManifest('testorg', 'pkg1', '1.0.0');
162
+ const pkg1v2 = createMockManifest('testorg', 'pkg1', '2.0.0');
163
+ const pkg2v1 = createMockManifest('testorg', 'pkg2', '1.0.0');
164
+ const pkg3v1 = createMockManifest('anotherorg', 'pkg3', '1.0.0');
165
+
166
+ await registry.publishPackage(pkg1v1, mockFileReader);
167
+ await registry.publishPackage(pkg1v2, mockFileReader);
168
+ await registry.publishPackage(pkg2v1, mockFileReader);
169
+ await registry.publishPackage(pkg3v1, mockFileReader);
170
+
171
+ // Update registry to create overviews
172
+ await registry.updateIfNeeded('normal');
173
+
174
+ // Verify initial state
175
+ let globalOverview = await registry.getGlobalOverview();
176
+ expect(globalOverview?.packages).toHaveLength(3); // testorg:pkg1, testorg:pkg2, anotherorg:pkg3
177
+
178
+ let pkg1Overview = await registry.getPackageOverview({ organization: 'testorg', name: 'pkg1' });
179
+ expect(pkg1Overview?.versions).toHaveLength(2); // v1.0.0 and v2.0.0
180
+
181
+ let pkg2Overview = await registry.getPackageOverview({ organization: 'testorg', name: 'pkg2' });
182
+ expect(pkg2Overview?.versions).toHaveLength(1); // v1.0.0
183
+
184
+ let pkg3Overview = await registry.getPackageOverview({ organization: 'anotherorg', name: 'pkg3' });
185
+ expect(pkg3Overview?.versions).toHaveLength(1); // v1.0.0
186
+
187
+ // 2. Manually delete some packages/versions from storage (simulating external deletion)
188
+ // Delete pkg1 v1.0.0
189
+ await storage.deleteFiles('v2/testorg/pkg1/1.0.0/manifest.json', 'v2/testorg/pkg1/1.0.0/model.json');
190
+
191
+ // Delete entire pkg2
192
+ await storage.deleteFiles('v2/testorg/pkg2/1.0.0/manifest.json', 'v2/testorg/pkg2/1.0.0/model.json');
193
+
194
+ // Leave pkg1 v2.0.0 and pkg3 v1.0.0 intact
195
+
196
+ // 3. Count snapshots before force mode
197
+ const initialSnapshots = await storage.listFiles('_overview_snapshots_v2/');
198
+
199
+ // 4. Run force mode - should create pre-write snapshots and rebuild from scratch
200
+ await registry.updateIfNeeded('force');
201
+
202
+ // 5. Verify pre-write snapshots were created
203
+ const finalSnapshots = await storage.listFiles('_overview_snapshots_v2/');
204
+ expect(finalSnapshots.length).toBeGreaterThan(initialSnapshots.length);
205
+
206
+ // Check for pre-write snapshots (should contain "-prewrite-" in filename)
207
+ const preWriteSnapshots = finalSnapshots.filter(s => s.includes('-prewrite-'));
208
+ expect(preWriteSnapshots.length).toBeGreaterThan(0);
209
+
210
+ // 6. Verify overviews now only reflect what exists in storage
211
+ globalOverview = await registry.getGlobalOverview();
212
+ expect(globalOverview?.packages).toHaveLength(2); // Only testorg:pkg1 and anotherorg:pkg3 should remain
213
+
214
+ const remainingPackageNames = globalOverview?.packages.map(p => `${p.id.organization}:${p.id.name}`).sort();
215
+ expect(remainingPackageNames).toEqual(['anotherorg:pkg3', 'testorg:pkg1']);
216
+
217
+ // 7. Verify pkg1 now only has v2.0.0
218
+ pkg1Overview = await registry.getPackageOverview({ organization: 'testorg', name: 'pkg1' });
219
+ expect(pkg1Overview?.versions).toHaveLength(1);
220
+ expect(pkg1Overview?.versions[0].description.id.version).toBe('2.0.0');
221
+
222
+ // 8. Verify pkg2 overview is unchanged (since pkg2 was completely deleted,
223
+ // force mode doesn't process it, so the old overview file remains)
224
+ pkg2Overview = await registry.getPackageOverview({ organization: 'testorg', name: 'pkg2' });
225
+ expect(pkg2Overview?.versions).toHaveLength(1); // Old overview remains
226
+
227
+ // 9. Verify pkg3 is unchanged
228
+ pkg3Overview = await registry.getPackageOverview({ organization: 'anotherorg', name: 'pkg3' });
229
+ expect(pkg3Overview?.versions).toHaveLength(1);
230
+ expect(pkg3Overview?.versions[0].description.id.version).toBe('1.0.0');
231
+
232
+ } finally {
233
+ await teardown();
234
+ }
63
235
  });
64
236
 
65
237
  test.each([