Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/src/content/docs/targets/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Avoid having multiple `registry` targets—it supports batching multiple apps an
| `linkPrereleases` | Update for preview releases. Default: `false` |
| `checksums` | List of checksum configs |
| `onlyIfPresent` | Only run if matching file exists |
| `name` | Human-readable name (used when creating new packages) |
| `packageUrl` | Link to package registry page, e.g., npmjs.com (used when creating new packages) |
| `mainDocsUrl` | Link to main documentation (used when creating new packages) |
| `apiDocsUrl` | Link to API documentation (used when creating new packages) |

### Checksum Configuration

Expand Down Expand Up @@ -52,3 +56,22 @@ targets:

- **sdk**: Package uploaded to public registries (PyPI, NPM, etc.)
- **app**: Standalone application with version files in the registry

## Creating New Packages

When you introduce a new package that doesn't yet exist in the release registry, Craft will automatically create the required directory structure and initial manifest.

```yaml
targets:
- name: registry
sdks:
'npm:@sentry/wasm':
name: 'Sentry WASM'
packageUrl: 'https://www.npmjs.com/package/@sentry/wasm'
mainDocsUrl: 'https://docs.sentry.io/platforms/javascript/'
urlTemplate: 'https://example.com/{{version}}/{{file}}'
```

## Manifest Metadata

The `repo_url` field is always derived from your GitHub repository configuration. When specified, the metadata fields (`name`, `packageUrl`, `mainDocsUrl`, `apiDocsUrl`) are applied to every release, allowing you to update package metadata by changing your `.craft.yml` configuration.
104 changes: 103 additions & 1 deletion src/targets/__tests__/registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vi, type Mock, type MockInstance, type Mocked, type MockedFunction } from 'vitest';
import { vi, type Mock, type MockedFunction } from 'vitest';
vi.mock('../../utils/githubApi.ts');
import { getGitHubClient } from '../../utils/githubApi';
import { RegistryConfig, RegistryTarget } from '../registry';
Expand Down Expand Up @@ -46,4 +46,106 @@ describe('getUpdatedManifest', () => {
// check if property created_at exists
expect(updatedManifest).toHaveProperty('created_at');
});

it('always sets repo_url from githubRepo config', async () => {
const registryConfig: RegistryConfig = {
type: RegistryPackageType.SDK,
canonicalName: 'example-package',
};
const packageManifest = {
canonical: 'example-package',
repo_url: 'https://github.com/old/repo',
};

const updatedManifest = await target.getUpdatedManifest(
registryConfig,
packageManifest,
'example-package',
'1.0.0',
'abc123'
);

expect(updatedManifest.repo_url).toBe(
'https://github.com/testSourceOwner/testSourceRepo'
);
});

it('applies config metadata fields to manifest', async () => {
const registryConfig: RegistryConfig = {
type: RegistryPackageType.SDK,
canonicalName: 'example-package',
name: 'Example Package',
packageUrl: 'https://npmjs.com/package/example',
mainDocsUrl: 'https://docs.example.com',
apiDocsUrl: 'https://api.example.com/docs',
};
const packageManifest = {
canonical: 'example-package',
};

const updatedManifest = await target.getUpdatedManifest(
registryConfig,
packageManifest,
'example-package',
'1.0.0',
'abc123'
);

expect(updatedManifest.name).toBe('Example Package');
expect(updatedManifest.package_url).toBe('https://npmjs.com/package/example');
expect(updatedManifest.main_docs_url).toBe('https://docs.example.com');
expect(updatedManifest.api_docs_url).toBe('https://api.example.com/docs');
});

it('config metadata fields override existing manifest values', async () => {
const registryConfig: RegistryConfig = {
type: RegistryPackageType.SDK,
canonicalName: 'example-package',
name: 'New Name',
mainDocsUrl: 'https://new-docs.example.com',
};
const packageManifest = {
canonical: 'example-package',
name: 'Old Name',
main_docs_url: 'https://old-docs.example.com',
package_url: 'https://npmjs.com/package/example',
};

const updatedManifest = await target.getUpdatedManifest(
registryConfig,
packageManifest,
'example-package',
'1.0.0',
'abc123'
);

// Config values should override
expect(updatedManifest.name).toBe('New Name');
expect(updatedManifest.main_docs_url).toBe('https://new-docs.example.com');
// Existing value not in config should be preserved
expect(updatedManifest.package_url).toBe('https://npmjs.com/package/example');
});

it('does not set optional fields when not specified in config', async () => {
const registryConfig: RegistryConfig = {
type: RegistryPackageType.SDK,
canonicalName: 'example-package',
};
const packageManifest = {
canonical: 'example-package',
};

const updatedManifest = await target.getUpdatedManifest(
registryConfig,
packageManifest,
'example-package',
'1.0.0',
'abc123'
);

expect(updatedManifest.name).toBeUndefined();
expect(updatedManifest.package_url).toBeUndefined();
expect(updatedManifest.main_docs_url).toBeUndefined();
expect(updatedManifest.api_docs_url).toBeUndefined();
});
});
50 changes: 49 additions & 1 deletion src/targets/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getPackageManifest,
updateManifestSymlinks,
RegistryPackageType,
InitialManifestData,
} from '../utils/registry';
import { isDryRun } from '../utils/helpers';
import { filterAsync, withRetry } from '../utils/async';
Expand All @@ -47,6 +48,14 @@ export interface RegistryConfig {
checksums?: ChecksumEntry[];
/** Pattern that allows to skip the target if there's no matching file */
onlyIfPresent?: RegExp;
/** Human-readable name for new packages */
name?: string;
/** Link to package registry (PyPI, npm, etc.) */
packageUrl?: string;
/** Link to main documentation */
mainDocsUrl?: string;
/** Link to API documentation */
apiDocsUrl?: string;
}

interface LocalRegistry {
Expand Down Expand Up @@ -371,6 +380,23 @@ export class RegistryTarget extends BaseTarget {
created_at: new Date().toISOString(),
};

// Apply config fields - these always override existing values when specified
// This allows repo maintainers to update metadata in their config
const { owner, repo } = this.githubRepo;
updatedManifest.repo_url = `https://github.com/${owner}/${repo}`;
if (registryConfig.name !== undefined) {
updatedManifest.name = registryConfig.name;
}
if (registryConfig.packageUrl !== undefined) {
updatedManifest.package_url = registryConfig.packageUrl;
}
if (registryConfig.mainDocsUrl !== undefined) {
updatedManifest.main_docs_url = registryConfig.mainDocsUrl;
}
if (registryConfig.apiDocsUrl !== undefined) {
updatedManifest.api_docs_url = registryConfig.apiDocsUrl;
}

// Add file links if it's a generic app (legacy)
if (registryConfig.type === RegistryPackageType.APP) {
await this.addFileLinks(
Expand All @@ -387,6 +413,26 @@ export class RegistryTarget extends BaseTarget {
return updatedManifest;
}

/**
* Builds the initial manifest data for creating a new package in the registry.
*
* @param registryConfig The registry configuration
* @returns The initial manifest data
*/
private buildInitialManifestData(
registryConfig: RegistryConfig
): InitialManifestData {
const { owner, repo } = this.githubRepo;
return {
canonical: registryConfig.canonicalName,
repoUrl: `https://github.com/${owner}/${repo}`,
name: registryConfig.name,
packageUrl: registryConfig.packageUrl,
mainDocsUrl: registryConfig.mainDocsUrl,
apiDocsUrl: registryConfig.apiDocsUrl,
};
}

/**
* Commits the new version of the package to the release registry.
*
Expand All @@ -401,11 +447,13 @@ export class RegistryTarget extends BaseTarget {
revision: string
): Promise<void> {
const canonicalName = registryConfig.canonicalName;
const initialManifestData = this.buildInitialManifestData(registryConfig);
const { versionFilePath, packageManifest } = await getPackageManifest(
localRepo.dir,
registryConfig.type,
canonicalName,
version
version,
initialManifestData
);

const newManifest = await this.getUpdatedManifest(
Expand Down
Loading
Loading