refactor: publish skills directly from synced vendor
This commit is contained in:
parent
270a9af804
commit
05dba5c320
|
|
@ -57,8 +57,8 @@ Supported hooks:
|
||||||
|
|
||||||
| Hook | Purpose | Expected Responsibility |
|
| Hook | Purpose | Expected Responsibility |
|
||||||
|------|---------|-------------------------|
|
|------|---------|-------------------------|
|
||||||
| `prepare_artifact` | Build a releasable artifact for one target | Validate the target is self-contained, stage files, apply any project-specific packaging |
|
| `prepare_artifact` | Make one target releasable | Validate the target is self-contained, sync/embed local dependencies, optionally stage extra files |
|
||||||
| `publish_artifact` | Publish one prepared artifact | Upload artifact, attach version/changelog/tags |
|
| `publish_artifact` | Publish one releasable target | Upload the prepared target (or a staged directory if the project uses one), attach version/changelog/tags |
|
||||||
|
|
||||||
Supported placeholders:
|
Supported placeholders:
|
||||||
|
|
||||||
|
|
@ -66,14 +66,14 @@ Supported placeholders:
|
||||||
|-------------|---------|
|
|-------------|---------|
|
||||||
| `{project_root}` | Absolute path to repository root |
|
| `{project_root}` | Absolute path to repository root |
|
||||||
| `{target}` | Absolute path to the module/skill being released |
|
| `{target}` | Absolute path to the module/skill being released |
|
||||||
| `{artifact_dir}` | Absolute path to a temporary artifact directory for this target |
|
| `{artifact_dir}` | Absolute path to a temporary staging directory for this target, when the project uses one |
|
||||||
| `{version}` | Version selected by the release workflow |
|
| `{version}` | Version selected by the release workflow |
|
||||||
| `{dry_run}` | `true` or `false` |
|
| `{dry_run}` | `true` or `false` |
|
||||||
| `{release_notes_file}` | Absolute path to a UTF-8 file containing release notes/changelog text |
|
| `{release_notes_file}` | Absolute path to a UTF-8 file containing release notes/changelog text |
|
||||||
|
|
||||||
Execution rules:
|
Execution rules:
|
||||||
- Keep the skill generic: do not hardcode registry/package-manager/project layout details into this SKILL.
|
- Keep the skill generic: do not hardcode registry/package-manager/project layout details into this SKILL.
|
||||||
- If `prepare_artifact` exists, run it once per target before publish-related checks that need the final artifact.
|
- If `prepare_artifact` exists, run it once per target before publish-related checks that need the final releasable target state.
|
||||||
- Write release notes to a temp file and pass that file path to `publish_artifact`; do not inline multiline changelog text into shell commands.
|
- Write release notes to a temp file and pass that file path to `publish_artifact`; do not inline multiline changelog text into shell commands.
|
||||||
- If hooks are absent, fall back to the default project-agnostic release workflow.
|
- If hooks are absent, fall back to the default project-agnostic release workflow.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
release:
|
release:
|
||||||
target_globs:
|
target_globs:
|
||||||
- skills/*
|
- skills/*
|
||||||
artifact_root: .release-artifacts
|
|
||||||
hooks:
|
hooks:
|
||||||
prepare_artifact: bun scripts/prepare-skill-artifact.mjs --skill-dir "{target}" --out-dir "{artifact_dir}"
|
prepare_artifact: node scripts/sync-shared-skill-packages.mjs --repo-root "{project_root}" --target "{target}"
|
||||||
publish_artifact: node scripts/publish-skill-artifact.mjs --skill-dir "{target}" --artifact-dir "{artifact_dir}" --version "{version}" --changelog-file "{release_notes_file}" --dry-run "{dry_run}"
|
publish_artifact: node scripts/publish-skill.mjs --skill-dir "{target}" --version "{version}" --changelog-file "{release_notes_file}" --dry-run "{dry_run}"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Skills organized into three categories in `.claude-plugin/marketplace.json` (def
|
||||||
|
|
||||||
Each skill contains `SKILL.md` (YAML front matter + docs), optional `scripts/`, `references/`, `prompts/`.
|
Each skill contains `SKILL.md` (YAML front matter + docs), optional `scripts/`, `references/`, `prompts/`.
|
||||||
|
|
||||||
Top-level `scripts/` contains repo maintenance utilities (sync, hooks, artifact build/publish).
|
Top-level `scripts/` contains repo maintenance utilities (sync, hooks, publish).
|
||||||
|
|
||||||
## Running Skills
|
## Running Skills
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ bash scripts/sync-clawhub.sh # sync all skills
|
||||||
bash scripts/sync-clawhub.sh <skill> # sync one skill
|
bash scripts/sync-clawhub.sh <skill> # sync one skill
|
||||||
```
|
```
|
||||||
|
|
||||||
Release-time artifact preparation is configured via `.releaserc.yml`. Keep registry/project-specific packaging in hook scripts.
|
Release hooks are configured via `.releaserc.yml`. This repo does not stage a separate release directory: release prep only syncs `packages/` into each skill's committed `scripts/vendor/`, and publish reads the skill directory directly.
|
||||||
|
|
||||||
## Shared Workspace Packages
|
## Shared Workspace Packages
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,6 @@
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
const TEXT_EXTENSIONS = new Set([
|
|
||||||
"md",
|
|
||||||
"mdx",
|
|
||||||
"txt",
|
|
||||||
"json",
|
|
||||||
"json5",
|
|
||||||
"yaml",
|
|
||||||
"yml",
|
|
||||||
"toml",
|
|
||||||
"js",
|
|
||||||
"cjs",
|
|
||||||
"mjs",
|
|
||||||
"ts",
|
|
||||||
"tsx",
|
|
||||||
"jsx",
|
|
||||||
"py",
|
|
||||||
"sh",
|
|
||||||
"rb",
|
|
||||||
"go",
|
|
||||||
"rs",
|
|
||||||
"swift",
|
|
||||||
"kt",
|
|
||||||
"java",
|
|
||||||
"cs",
|
|
||||||
"cpp",
|
|
||||||
"c",
|
|
||||||
"h",
|
|
||||||
"hpp",
|
|
||||||
"sql",
|
|
||||||
"csv",
|
|
||||||
"ini",
|
|
||||||
"cfg",
|
|
||||||
"env",
|
|
||||||
"xml",
|
|
||||||
"html",
|
|
||||||
"css",
|
|
||||||
"scss",
|
|
||||||
"sass",
|
|
||||||
"svg",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const PACKAGE_DEPENDENCY_SECTIONS = [
|
const PACKAGE_DEPENDENCY_SECTIONS = [
|
||||||
"dependencies",
|
"dependencies",
|
||||||
"optionalDependencies",
|
"optionalDependencies",
|
||||||
|
|
@ -49,15 +8,18 @@ const PACKAGE_DEPENDENCY_SECTIONS = [
|
||||||
"devDependencies",
|
"devDependencies",
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function listTextFiles(root) {
|
const SKIPPED_DIRS = new Set([".git", ".clawhub", ".clawdhub", "node_modules"]);
|
||||||
|
const SKIPPED_FILES = new Set([".DS_Store"]);
|
||||||
|
|
||||||
|
export async function listReleaseFiles(root) {
|
||||||
|
const resolvedRoot = path.resolve(root);
|
||||||
const files = [];
|
const files = [];
|
||||||
|
|
||||||
async function walk(folder) {
|
async function walk(folder) {
|
||||||
const entries = await fs.readdir(folder, { withFileTypes: true });
|
const entries = await fs.readdir(folder, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.name.startsWith(".")) continue;
|
if (entry.isDirectory() && SKIPPED_DIRS.has(entry.name)) continue;
|
||||||
if (entry.name === "node_modules") continue;
|
if (entry.isFile() && SKIPPED_FILES.has(entry.name)) continue;
|
||||||
if (entry.name === ".clawhub" || entry.name === ".clawdhub") continue;
|
|
||||||
|
|
||||||
const fullPath = path.join(folder, entry.name);
|
const fullPath = path.join(folder, entry.name);
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
|
|
@ -66,36 +28,19 @@ export async function listTextFiles(root) {
|
||||||
}
|
}
|
||||||
if (!entry.isFile()) continue;
|
if (!entry.isFile()) continue;
|
||||||
|
|
||||||
const relPath = path.relative(root, fullPath).split(path.sep).join("/");
|
const relPath = path.relative(resolvedRoot, fullPath).split(path.sep).join("/");
|
||||||
const ext = relPath.split(".").pop()?.toLowerCase() ?? "";
|
|
||||||
if (!TEXT_EXTENSIONS.has(ext)) continue;
|
|
||||||
|
|
||||||
const bytes = await fs.readFile(fullPath);
|
const bytes = await fs.readFile(fullPath);
|
||||||
files.push({ relPath, bytes });
|
files.push({ relPath, bytes });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await walk(root);
|
await walk(resolvedRoot);
|
||||||
files.sort((left, right) => left.relPath.localeCompare(right.relPath));
|
files.sort((left, right) => left.relPath.localeCompare(right.relPath));
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function collectReleaseFiles(root) {
|
|
||||||
await validateSelfContainedRelease(root);
|
|
||||||
return listTextFiles(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function materializeReleaseFiles(files, outDir) {
|
|
||||||
await fs.mkdir(outDir, { recursive: true });
|
|
||||||
for (const file of files) {
|
|
||||||
const outputPath = path.join(outDir, fromPosixRel(file.relPath));
|
|
||||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
||||||
await fs.writeFile(outputPath, file.bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function validateSelfContainedRelease(root) {
|
export async function validateSelfContainedRelease(root) {
|
||||||
const files = await listTextFiles(root);
|
const files = await listReleaseFiles(root);
|
||||||
for (const file of files.filter((entry) => path.posix.basename(entry.relPath) === "package.json")) {
|
for (const file of files.filter((entry) => path.posix.basename(entry.relPath) === "package.json")) {
|
||||||
const packageDir = path.resolve(root, fromPosixRel(path.posix.dirname(file.relPath)));
|
const packageDir = path.resolve(root, fromPosixRel(path.posix.dirname(file.relPath)));
|
||||||
const packageJson = JSON.parse(file.bytes.toString("utf8"));
|
const packageJson = JSON.parse(file.bytes.toString("utf8"));
|
||||||
|
|
@ -108,7 +53,7 @@ export async function validateSelfContainedRelease(root) {
|
||||||
const targetDir = path.resolve(packageDir, spec.slice(5));
|
const targetDir = path.resolve(packageDir, spec.slice(5));
|
||||||
if (!isWithinRoot(root, targetDir)) {
|
if (!isWithinRoot(root, targetDir)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Release artifact is not self-contained: ${file.relPath} depends on ${name} via ${spec}`,
|
`Release target is not self-contained: ${file.relPath} depends on ${name} via ${spec}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await fs.access(targetDir).catch(() => {
|
await fs.access(targetDir).catch(() => {
|
||||||
|
|
@ -16,7 +16,8 @@ const SKIPPED_FILES = new Set([".DS_Store"]);
|
||||||
export async function syncSharedSkillPackages(repoRoot, options = {}) {
|
export async function syncSharedSkillPackages(repoRoot, options = {}) {
|
||||||
const root = path.resolve(repoRoot);
|
const root = path.resolve(repoRoot);
|
||||||
const workspacePackages = await discoverWorkspacePackages(root);
|
const workspacePackages = await discoverWorkspacePackages(root);
|
||||||
const consumers = await discoverSkillScriptPackages(root);
|
const targetConsumerDirs = normalizeTargetConsumerDirs(root, options.targets ?? []);
|
||||||
|
const consumers = await discoverSkillScriptPackages(root, targetConsumerDirs);
|
||||||
const runtime = options.install === false ? null : resolveBunRuntime();
|
const runtime = options.install === false ? null : resolveBunRuntime();
|
||||||
const managedPaths = new Set();
|
const managedPaths = new Set();
|
||||||
const packageDirs = [];
|
const packageDirs = [];
|
||||||
|
|
@ -42,6 +43,25 @@ export async function syncSharedSkillPackages(repoRoot, options = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeTargetConsumerDirs(repoRoot, targets) {
|
||||||
|
if (!targets || targets.length === 0) return null;
|
||||||
|
|
||||||
|
const consumerDirs = new Set();
|
||||||
|
for (const target of targets) {
|
||||||
|
if (!target) continue;
|
||||||
|
|
||||||
|
const resolvedTarget = path.resolve(repoRoot, target);
|
||||||
|
if (path.basename(resolvedTarget) === "scripts") {
|
||||||
|
consumerDirs.add(resolvedTarget);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumerDirs.add(path.join(resolvedTarget, "scripts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumerDirs;
|
||||||
|
}
|
||||||
|
|
||||||
export function ensureManagedPathsClean(repoRoot, managedPaths) {
|
export function ensureManagedPathsClean(repoRoot, managedPaths) {
|
||||||
if (managedPaths.length === 0) return;
|
if (managedPaths.length === 0) return;
|
||||||
|
|
||||||
|
|
@ -182,13 +202,14 @@ async function discoverWorkspacePackages(repoRoot) {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function discoverSkillScriptPackages(repoRoot) {
|
async function discoverSkillScriptPackages(repoRoot, targetConsumerDirs = null) {
|
||||||
const skillsRoot = path.join(repoRoot, "skills");
|
const skillsRoot = path.join(repoRoot, "skills");
|
||||||
const consumers = [];
|
const consumers = [];
|
||||||
const skillEntries = await fs.readdir(skillsRoot, { withFileTypes: true });
|
const skillEntries = await fs.readdir(skillsRoot, { withFileTypes: true });
|
||||||
for (const entry of skillEntries) {
|
for (const entry of skillEntries) {
|
||||||
if (!entry.isDirectory()) continue;
|
if (!entry.isDirectory()) continue;
|
||||||
const scriptsDir = path.join(skillsRoot, entry.name, "scripts");
|
const scriptsDir = path.join(skillsRoot, entry.name, "scripts");
|
||||||
|
if (targetConsumerDirs && !targetConsumerDirs.has(path.resolve(scriptsDir))) continue;
|
||||||
const packageJsonPath = path.join(scriptsDir, "package.json");
|
const packageJsonPath = path.join(scriptsDir, "package.json");
|
||||||
if (!existsSync(packageJsonPath)) continue;
|
if (!existsSync(packageJsonPath)) continue;
|
||||||
consumers.push({ dir: scriptsDir, packageJsonPath });
|
consumers.push({ dir: scriptsDir, packageJsonPath });
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
import { collectReleaseFiles, materializeReleaseFiles } from "./lib/skill-artifact.mjs";
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const options = parseArgs(process.argv.slice(2));
|
|
||||||
if (!options.skillDir || !options.outDir) {
|
|
||||||
throw new Error("--skill-dir and --out-dir are required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const skillDir = path.resolve(options.skillDir);
|
|
||||||
const outDir = path.resolve(options.outDir);
|
|
||||||
const files = await collectReleaseFiles(skillDir);
|
|
||||||
await materializeReleaseFiles(files, outDir);
|
|
||||||
|
|
||||||
console.log(`Prepared artifact for ${path.basename(skillDir)}`);
|
|
||||||
console.log(`Source: ${skillDir}`);
|
|
||||||
console.log(`Output: ${outDir}`);
|
|
||||||
console.log(`Files: ${files.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(argv) {
|
|
||||||
const options = {
|
|
||||||
skillDir: "",
|
|
||||||
outDir: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let index = 0; index < argv.length; index += 1) {
|
|
||||||
const arg = argv[index];
|
|
||||||
if (arg === "--skill-dir") {
|
|
||||||
options.skillDir = argv[index + 1] ?? "";
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === "--out-dir") {
|
|
||||||
options.outDir = argv[index + 1] ?? "";
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === "-h" || arg === "--help") {
|
|
||||||
printUsage();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
throw new Error(`Unknown argument: ${arg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
function printUsage() {
|
|
||||||
console.log(`Usage: prepare-skill-artifact.mjs --skill-dir <dir> --out-dir <dir>
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--skill-dir <dir> Source skill directory
|
|
||||||
--out-dir <dir> Artifact output directory
|
|
||||||
-h, --help Show help`);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error instanceof Error ? error.message : String(error));
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
@ -5,31 +5,31 @@ import { existsSync } from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { listTextFiles } from "./lib/skill-artifact.mjs";
|
import { listReleaseFiles, validateSelfContainedRelease } from "./lib/release-files.mjs";
|
||||||
|
|
||||||
const DEFAULT_REGISTRY = "https://clawhub.ai";
|
const DEFAULT_REGISTRY = "https://clawhub.ai";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const options = parseArgs(process.argv.slice(2));
|
const options = parseArgs(process.argv.slice(2));
|
||||||
if (!options.skillDir || !options.artifactDir || !options.version) {
|
if (!options.skillDir || !options.version) {
|
||||||
throw new Error("--skill-dir, --artifact-dir, and --version are required");
|
throw new Error("--skill-dir and --version are required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const skillDir = path.resolve(options.skillDir);
|
const skillDir = path.resolve(options.skillDir);
|
||||||
const artifactDir = path.resolve(options.artifactDir);
|
|
||||||
const skill = buildSkillEntry(skillDir, options.slug, options.displayName);
|
const skill = buildSkillEntry(skillDir, options.slug, options.displayName);
|
||||||
const changelog = options.changelogFile
|
const changelog = options.changelogFile
|
||||||
? await fs.readFile(path.resolve(options.changelogFile), "utf8")
|
? await fs.readFile(path.resolve(options.changelogFile), "utf8")
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const files = await listTextFiles(artifactDir);
|
await validateSelfContainedRelease(skillDir);
|
||||||
|
const files = await listReleaseFiles(skillDir);
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
throw new Error(`Artifact directory is empty: ${artifactDir}`);
|
throw new Error(`Skill directory is empty: ${skillDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.dryRun) {
|
if (options.dryRun) {
|
||||||
console.log(`Dry run: would publish ${skill.slug}@${options.version}`);
|
console.log(`Dry run: would publish ${skill.slug}@${options.version}`);
|
||||||
console.log(`Artifact: ${artifactDir}`);
|
console.log(`Skill: ${skillDir}`);
|
||||||
console.log(`Files: ${files.length}`);
|
console.log(`Files: ${files.length}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +68,6 @@ async function main() {
|
||||||
function parseArgs(argv) {
|
function parseArgs(argv) {
|
||||||
const options = {
|
const options = {
|
||||||
skillDir: "",
|
skillDir: "",
|
||||||
artifactDir: "",
|
|
||||||
version: "",
|
version: "",
|
||||||
changelogFile: "",
|
changelogFile: "",
|
||||||
registry: "",
|
registry: "",
|
||||||
|
|
@ -85,11 +84,6 @@ function parseArgs(argv) {
|
||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (arg === "--artifact-dir") {
|
|
||||||
options.artifactDir = argv[index + 1] ?? "";
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === "--version") {
|
if (arg === "--version") {
|
||||||
options.version = argv[index + 1] ?? "";
|
options.version = argv[index + 1] ?? "";
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|
@ -141,11 +135,10 @@ function parseArgs(argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function printUsage() {
|
function printUsage() {
|
||||||
console.log(`Usage: publish-skill-artifact.mjs --skill-dir <dir> --artifact-dir <dir> --version <semver> [options]
|
console.log(`Usage: publish-skill.mjs --skill-dir <dir> --version <semver> [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--skill-dir <dir> Source skill directory (used for slug/display name)
|
--skill-dir <dir> Skill directory to publish
|
||||||
--artifact-dir <dir> Prepared artifact directory
|
|
||||||
--version <semver> Version to publish
|
--version <semver> Version to publish
|
||||||
--changelog-file <file> Release notes file
|
--changelog-file <file> Release notes file
|
||||||
--registry <url> Override registry base URL
|
--registry <url> Override registry base URL
|
||||||
|
|
@ -227,7 +220,7 @@ async function publishSkill({ registry, token, skill, files, version, changelog,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
form.append("files", new Blob([file.bytes], { type: "text/plain" }), file.relPath);
|
form.append("files", new Blob([file.bytes], { type: "application/octet-stream" }), file.relPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${registry}/api/v1/skills`, {
|
const response = await fetch(`${registry}/api/v1/skills`, {
|
||||||
|
|
@ -10,7 +10,9 @@ import {
|
||||||
async function main() {
|
async function main() {
|
||||||
const options = parseArgs(process.argv.slice(2));
|
const options = parseArgs(process.argv.slice(2));
|
||||||
const repoRoot = path.resolve(options.repoRoot);
|
const repoRoot = path.resolve(options.repoRoot);
|
||||||
const result = await syncSharedSkillPackages(repoRoot);
|
const result = await syncSharedSkillPackages(repoRoot, {
|
||||||
|
targets: options.targets,
|
||||||
|
});
|
||||||
|
|
||||||
if (options.enforceClean) {
|
if (options.enforceClean) {
|
||||||
ensureManagedPathsClean(repoRoot, result.managedPaths);
|
ensureManagedPathsClean(repoRoot, result.managedPaths);
|
||||||
|
|
@ -23,6 +25,7 @@ function parseArgs(argv) {
|
||||||
const options = {
|
const options = {
|
||||||
repoRoot: process.cwd(),
|
repoRoot: process.cwd(),
|
||||||
enforceClean: false,
|
enforceClean: false,
|
||||||
|
targets: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let index = 0; index < argv.length; index += 1) {
|
for (let index = 0; index < argv.length; index += 1) {
|
||||||
|
|
@ -36,6 +39,11 @@ function parseArgs(argv) {
|
||||||
options.enforceClean = true;
|
options.enforceClean = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (arg === "--target") {
|
||||||
|
options.targets.push(argv[index + 1] ?? "");
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (arg === "-h" || arg === "--help") {
|
if (arg === "-h" || arg === "--help") {
|
||||||
printUsage();
|
printUsage();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|
@ -51,6 +59,7 @@ function printUsage() {
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--repo-root <dir> Repository root (default: current directory)
|
--repo-root <dir> Repository root (default: current directory)
|
||||||
|
--target <dir> Sync only one skill directory (can be repeated)
|
||||||
--enforce-clean Fail if managed files change after sync
|
--enforce-clean Fail if managed files change after sync
|
||||||
-h, --help Show help`);
|
-h, --help Show help`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue