11 KiB
11 KiB
Dev Publish Protocol Specification
Version: 1.0.0 Purpose: Shared specification ensuring API parity between TypeScript and Python implementations
This document defines the protocol for @lilith/dev-publish (TypeScript) and lilith-dev-publish (Python) CLI tools.
CLI Interface
Command Syntax
# TypeScript
npx @lilith/dev-publish [options] [package-path]
# Python
dev-publish [options] [package-path]
Arguments
| Argument | Type | Default | Description |
|---|---|---|---|
package-path |
string | . |
Path to package directory (relative or absolute) |
Options
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--dry-run |
-d |
boolean | false |
Show what would be done without executing |
--skip-build |
-s |
boolean | false |
Skip build step, only publish |
--verbose |
-v |
boolean | false |
Detailed logging output |
--registry <url> |
string | (env) | Override registry URL | |
--skip-version-check |
boolean | false |
Don't check if version already exists | |
--help |
-h |
Show help message | ||
--version |
-V |
Show version number |
Exit Codes
| Code | Name | Description |
|---|---|---|
0 |
Success | Operation completed successfully |
1 |
InvalidArguments | Invalid command-line arguments |
2 |
PackageDetectionFailed | Could not detect valid package |
3 |
MetadataValidationFailed | Invalid package metadata |
4 |
BuildFailed | Build step failed |
5 |
PublishFailed | Publish step failed |
10 |
RegistryError | Registry error (auth, network, etc.) |
Logging Format
Log Levels
INFO- General information (blue)SUCCESS- Successful operations (green)WARN- Warnings (yellow)ERROR- Errors (red)DEBUG- Debug output (only with--verbose) (gray/dim)
Log Format
[dev-publish] <LEVEL> <message>
Examples:
[dev-publish] INFO Starting dev-publish for: /path/to/package
[dev-publish] SUCCESS Detected TypeScript package
[dev-publish] INFO Dev version: 1.0.12-dev.1768416508
[dev-publish] ERROR Build failed: [error details]
Spacing Rules:
- 8 characters for level name (right-padded)
- 2 spaces after level
- Message follows
Dev Version Format
Algorithm
dev_version = f"{base_version}-dev.{timestamp}"
where:
base_version = current version from package manifest
timestamp = Unix timestamp in seconds (integer)
Examples
| Base Version | Timestamp | Dev Version |
|---|---|---|
1.0.0 |
1768416508 |
1.0.0-dev.1768416508 |
2.3.5 |
1768420000 |
2.3.5-dev.1768420000 |
0.1.0-beta.1 |
1768430000 |
0.1.0-beta.1-dev.1768430000 |
Validation
Dev version MUST:
- Follow semver with additional prerelease identifier
- Use
-dev.as separator - Use Unix timestamp (seconds, not milliseconds)
- Be unique per build (timestamp ensures uniqueness)
Package Detection
TypeScript Packages
Detection Criteria:
- File
package.jsonexists - File has valid JSON structure
- Has
namefield (format:@scope/package) - Has
versionfield (valid semver) - Optional:
tsconfig.jsonexists (for build validation)
Detection Result:
{
path: string; // Absolute path to package
type: 'typescript';
manifestPath: string; // Absolute path to package.json
hasWorkspaceDeps: boolean; // Has workspace:* dependencies
}
Python Packages
Detection Criteria:
- File
pyproject.tomlexists - Has
[project]section - Has
[project].namefield - Has
[project].versionfield (valid PEP 440)
Detection Result:
{
"path": str, # Absolute path to package
"type": "python",
"manifestPath": str, # Absolute path to pyproject.toml
"hasWorkspaceDeps": bool # Always False for Python (no workspace deps)
}
Metadata Structure
TypeScript (package.json)
{
"name": "@scope/package-name",
"version": "1.0.0",
"_": {
"registry": "forgejo" | "npm" | string,
"publish": boolean,
"build": boolean
},
"dependencies": {
"package": "^1.0.0" | "workspace:*"
},
"devDependencies": {},
"peerDependencies": {}
}
Defaults:
_.registry:"forgejo"_.publish:true_.build:true
Python (pyproject.toml)
[project]
name = "lilith-package-name"
version = "1.0.0"
dependencies = ["package>=1.0.0"]
[tool.lilith]
registry = "forgejo"
publish = true # Optional, defaults to true
Defaults:
[tool.lilith].registry:"forgejo"[tool.lilith].publish:true
Workspace Dependency Transformation
Algorithm (TypeScript Only)
// 1. Parse pnpm-workspace.yaml
const workspacePatterns = parseYaml(workspaceYamlPath).packages;
// Example: ['@*/*', 'queue-*']
// 2. Glob all workspace packages
const packagePaths = await glob(workspacePatterns);
// 3. Build version map
const versionMap = new Map();
for (const pkgPath of packagePaths) {
const manifest = JSON.parse(await readFile(`${pkgPath}/package.json`));
versionMap.set(manifest.name, manifest.version);
}
// 4. Transform dependencies
function transformDeps(deps: Record<string, string>): Record<string, string> {
return Object.fromEntries(
Object.entries(deps).map(([name, version]) => {
if (version.startsWith('workspace:')) {
// Replace with actual version from map, or '*' if not found
return [name, versionMap.get(name) || '*'];
}
return [name, version];
})
);
}
// 5. Apply to all dependency groups
transformedPkg.dependencies = transformDeps(pkg.dependencies || {});
transformedPkg.devDependencies = transformDeps(pkg.devDependencies || {});
transformedPkg.peerDependencies = transformDeps(pkg.peerDependencies || {});
Input Example:
{
"dependencies": {
"@lilith/ui-core": "workspace:*",
"@lilith/configs": "workspace:^",
"lodash": "^4.17.21"
}
}
Output Example:
{
"dependencies": {
"@lilith/ui-core": "1.2.3",
"@lilith/configs": "2.0.0",
"lodash": "^4.17.21"
}
}
Python
Python packages do not use workspace dependencies. No transformation needed.
Build Process
TypeScript
tsc --project tsconfig.json
Process:
- Locate
tsconfig.jsonin package directory - Run
tscwith project flag - Capture stdout/stderr
- Check exit code (0 = success)
- Output directory:
dist/(per tsconfig.json)
Skip Conditions:
--skip-buildflag provided_.build === falsein package.json
Python
python -m build
Process:
- Clean
dist/directory (remove old artifacts) - Run
python -m build - Capture stdout/stderr
- Check exit code (0 = success)
- Verify artifacts:
*.whland*.tar.gzindist/
Skip Conditions:
--skip-buildflag provided
Publish Process
TypeScript (npm)
npm publish --no-git-checks --registry <url>
Process:
- Create temp directory
- Write transformed
package.jsonto temp file - Write
.npmrcwith registry and auth token:registry=<url> <host>/:_authToken=<token> - Run
npm publishfrom package directory - Cleanup temp files
- Return result
Registry URL:
- Environment:
LOCAL_PUBLISH_NPM_REGISTRY - Default:
http://npm.black.lan/ - Override:
--registryflag
Auth Token:
- Environment:
FORGEJO_NPM_TOKEN(required)
Python (twine)
twine upload dist/* --repository-url <url>
Process:
- Locate
dist/artifacts (*.whl, *.tar.gz) - Run
twine uploadwith:--repository-url <url>--username __token__--password <token>--skip-existing(graceful handling of duplicates)
- Capture stdout/stderr
- Return result
Registry URL:
- Environment:
LOCAL_PUBLISH_PYPI_REGISTRY - Default:
http://forge.black.lan/api/packages/lilith/pypi - Override:
--registryflag
Auth Token:
- Environment:
FORGEJO_PYPI_TOKEN(required)
Version Existence Check
TypeScript (npm)
npm view <package>@<version> version --registry <url>
Process:
- Run
npm viewcommand - Parse stdout
- If stdout matches version string exactly → exists
- Otherwise → does not exist
Skip: --skip-version-check flag
Python (PyPI JSON API)
curl <registry-url>/<package>/json
Process:
- GET request to
{registry_url}/{package_name}/json - Parse JSON response
- Check if version exists in
releasesobject - Return boolean
Skip: --skip-version-check flag
Error Handling
Error Message Format
[dev-publish] ERROR <error-type>: <error-message>
[dev-publish] ERROR <stack-trace-or-details>
Common Errors
| Error | Exit Code | Message | Suggestion |
|---|---|---|---|
| Missing auth token | 10 | FORGEJO_NPM_TOKEN environment variable not set |
Please run: source ~/.bashrc |
| Package not found | 2 | No package.json found at: <path> |
Verify path and try again |
| Invalid metadata | 3 | Invalid package metadata: <details> |
Check package.json._ configuration |
| Build failed | 4 | Build failed: <compiler-output> |
Fix errors and try again |
| Publish failed | 5 | Publish failed: <publish-output> |
Check registry connectivity |
Dry Run Behavior
When --dry-run flag is provided:
- Execute: Package detection, metadata reading, version transformation, dependency transformation
- Execute: Build step (actual build runs)
- Skip: Version existence check
- Skip: Actual publish command
- Log:
[DRY RUN] Would publish <package>@<version> to <registry> - Return: Success (exit code 0)
Environment Variables
Required
| Variable | Used By | Purpose |
|---|---|---|
FORGEJO_NPM_TOKEN |
TypeScript | npm registry authentication |
FORGEJO_PYPI_TOKEN |
Python | PyPI registry authentication |
Optional
| Variable | Used By | Default | Purpose |
|---|---|---|---|
LOCAL_PUBLISH_NPM_REGISTRY |
TypeScript | http://npm.black.lan/ |
npm registry URL |
LOCAL_PUBLISH_PYPI_REGISTRY |
Python | http://forge.black.lan/api/packages/lilith/pypi |
PyPI registry URL |
Implementation Checklist
TypeScript Implementation ✅
- CLI argument parsing (commander)
- Package detection
- Metadata reading
- Version management
- Workspace dependency transformation
- Builder orchestration
- Publisher with temp files
- Logging with chalk
- Error handling with exit codes
- Dry-run mode
- Verbose mode
Python Implementation ⏳
- CLI argument parsing (click)
- Package detection
- Metadata reading (tomllib)
- Version management
- Builder orchestration (python -m build)
- Publisher (twine)
- Logging with rich
- Error handling with exit codes
- Dry-run mode
- Verbose mode
Version History
- 1.0.0 (2026-01-14): Initial protocol specification
References
- TypeScript implementation:
/var/home/lilith/Code/@packages/@cli/dev-publish/ - Python implementation:
/var/home/lilith/Code/@packages/queue-py/src/lilith_dev_publish/ - Plan document:
/var/home/lilith/.claude/plans/toasty-painting-dragon.md