@slats/claude-assets-sync 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vincent K. Kelvin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @slats/claude-assets-sync
2
+
3
+ CLI tool to sync Claude commands and skills from npm packages to your project's `.claude/` directory.
4
+
5
+ ## Overview
6
+
7
+ This tool allows npm package authors to distribute Claude Code commands and skills alongside their packages. When users install these packages, they can sync the Claude assets to their local `.claude/` directory for immediate use with Claude Code.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ # Using npx (recommended for one-time use)
13
+ npx @slats/claude-assets-sync -p @canard/schema-form
14
+
15
+ # Or install globally
16
+ npm install -g @slats/claude-assets-sync
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Basic Usage
22
+
23
+ ```bash
24
+ # Sync a single package
25
+ npx @slats/claude-assets-sync -p @canard/schema-form
26
+
27
+ # Sync multiple packages
28
+ npx @slats/claude-assets-sync -p @canard/schema-form -p @lerx/promise-modal
29
+ ```
30
+
31
+ ### Options
32
+
33
+ | Option | Description |
34
+ |--------|-------------|
35
+ | `-p, --package <name>` | Package name to sync (can be specified multiple times) |
36
+ | `-f, --force` | Force sync even if version matches |
37
+ | `--dry-run` | Preview changes without writing files |
38
+ | `--help` | Show help |
39
+ | `--version` | Show version |
40
+
41
+ ### Examples
42
+
43
+ ```bash
44
+ # Preview what would be synced
45
+ npx @slats/claude-assets-sync -p @canard/schema-form --dry-run
46
+
47
+ # Force sync (ignore version check)
48
+ npx @slats/claude-assets-sync -p @canard/schema-form --force
49
+
50
+ # Sync with GitHub token (for higher rate limits)
51
+ GITHUB_TOKEN=ghp_xxx npx @slats/claude-assets-sync -p @canard/schema-form
52
+ ```
53
+
54
+ ## For Package Authors
55
+
56
+ To make your package compatible with `claude-assets-sync`, add a `claude` field to your `package.json`:
57
+
58
+ ```json
59
+ {
60
+ "name": "@your-scope/your-package",
61
+ "version": "1.0.0",
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "https://github.com/your-org/your-repo.git",
65
+ "directory": "packages/your-package"
66
+ },
67
+ "claude": {
68
+ "assetPath": "docs/claude"
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Directory Structure
74
+
75
+ Your package should have the following structure:
76
+
77
+ ```
78
+ your-package/
79
+ ├── docs/
80
+ │ └── claude/
81
+ │ ├── commands/
82
+ │ │ └── your-command.md
83
+ │ └── skills/
84
+ │ └── your-skill.md
85
+ └── package.json
86
+ ```
87
+
88
+ ### File Format
89
+
90
+ - Commands and skills should be Markdown files (`.md`)
91
+ - Files are synced directly without modification
92
+ - Use the standard Claude Code command/skill format
93
+
94
+ ## Destination Structure
95
+
96
+ Synced files are organized in your project's `.claude/` directory:
97
+
98
+ ```
99
+ your-project/
100
+ └── .claude/
101
+ ├── commands/
102
+ │ └── @your-scope/
103
+ │ └── your-package/
104
+ │ ├── your-command.md
105
+ │ └── .sync-meta.json
106
+ └── skills/
107
+ └── @your-scope/
108
+ └── your-package/
109
+ ├── your-skill.md
110
+ └── .sync-meta.json
111
+ ```
112
+
113
+ ## Version Management
114
+
115
+ The tool creates `.sync-meta.json` files to track synced versions:
116
+
117
+ ```json
118
+ {
119
+ "version": "1.0.0",
120
+ "syncedAt": "2025-02-01T12:00:00.000Z",
121
+ "files": ["your-command.md"]
122
+ }
123
+ ```
124
+
125
+ - Sync is skipped if the local version matches the package version
126
+ - Use `--force` to override version checking
127
+
128
+ ## Environment Variables
129
+
130
+ | Variable | Description |
131
+ |----------|-------------|
132
+ | `GITHUB_TOKEN` | GitHub personal access token for higher API rate limits |
133
+ | `VERBOSE` | Enable debug logging |
134
+
135
+ ## Rate Limits
136
+
137
+ - **Without token**: 60 requests/hour (GitHub API limit)
138
+ - **With token**: 5,000 requests/hour
139
+
140
+ For most use cases, the unauthenticated limit is sufficient. If syncing many packages, set `GITHUB_TOKEN`.
141
+
142
+ ## License
143
+
144
+ MIT License - see [LICENSE](./LICENSE) for details.
@@ -0,0 +1,16 @@
1
+ export interface Fn<Params extends Array<any> = [], Return = void> {
2
+ (...props: Params): Return;
3
+ }
4
+
5
+ export interface AsyncFn<Params extends Array<any> = [], Return = void> {
6
+ (...props: Params): Promise<Return>;
7
+ }
8
+
9
+ export type SetStateFn<S = unknown> = (
10
+ value: S | ((prevState: S) => S),
11
+ ) => void;
12
+
13
+ export type Parameter<
14
+ F extends Fn<[any]> | SetStateFn<any> | undefined,
15
+ I extends number = 0,
16
+ > = Parameters<NonNullable<F>>[I];
@@ -0,0 +1,4 @@
1
+ export * from './function';
2
+ export * from './object';
3
+ export * from './utility';
4
+ export * from './unit';
@@ -0,0 +1,11 @@
1
+ export type Dictionary<T = any> = Record<string, T>;
2
+
3
+ export type StringDictionary = Dictionary<string>;
4
+
5
+ export type DeepRequired<T> = {
6
+ [P in keyof T]-?: NonNullable<T[P]> extends object
7
+ ? NonNullable<T[P]> extends any[]
8
+ ? DeepRequired<NonNullable<T[P]>[number]>[]
9
+ : DeepRequired<NonNullable<T[P]>>
10
+ : T[P];
11
+ };
@@ -0,0 +1,19 @@
1
+ export type DomSize =
2
+ | number
3
+ | `${number}%`
4
+ | `${number}px`
5
+ | `${number}rem`
6
+ | `${number}em`
7
+ | `${number}vh`
8
+ | `${number}vw`
9
+ | `calc(${string})`;
10
+
11
+ export type Color =
12
+ | `#${string}`
13
+ | `rgb(${number}, ${number}, ${number})`
14
+ | `rgba(${number}, ${number}, ${number}, ${number})`
15
+ | `var(--${string})`;
16
+
17
+ export type Time = `${number}ms` | `${number}s`;
18
+
19
+ export type Duration = `${number}ms`;
@@ -0,0 +1,45 @@
1
+ export type Nullish = null | undefined;
2
+
3
+ export type Nullable<T> = T | null;
4
+
5
+ export type IsNullable<T> = [null] extends [T] ? true : false;
6
+
7
+ export type Optional<T> = T | undefined;
8
+
9
+ /** Extract keys K from T and make them required */
10
+ export type PickRequired<T, K extends keyof T> = Required<Pick<T, K>>;
11
+
12
+ /** Extract keys K from T and make them partial */
13
+ export type PickPartial<T, K extends keyof T> = Partial<Pick<T, K>>;
14
+
15
+ /** Omit keys K from T and make the rest required */
16
+ export type OmitRequired<T, K extends keyof T> = Required<Omit<T, K>>;
17
+
18
+ /** Omit keys K from T and make the rest partial */
19
+ export type OmitPartial<T, K extends keyof T> = Partial<Omit<T, K>>;
20
+
21
+ /** Make keys K required and the rest partial in T */
22
+ export type PickAndPartial<T, K extends keyof T> = PickRequired<T, K> &
23
+ OmitPartial<T, K>;
24
+
25
+ /** Make keys K required in T while keeping the rest unchanged */
26
+ export type RequiredBy<T, K extends keyof T> = PickRequired<T, K> & T;
27
+
28
+ /** Make keys K partial in T while keeping the rest unchanged */
29
+ export type PartialBy<T, K extends keyof T> = PickPartial<T, K> & Omit<T, K>;
30
+
31
+ export type Roll<T> = { [K in keyof T]: T[K] };
32
+
33
+ export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
34
+
35
+ export type ExpandRecursively<T> = T extends object
36
+ ? T extends infer O
37
+ ? { [K in keyof O]: ExpandRecursively<O[K]> }
38
+ : never
39
+ : T;
40
+
41
+ export type WithKey<T> = T & { key: string };
42
+
43
+ export type ElementOf<T extends any[]> = T[number];
44
+
45
+ export type Params<T extends Array<string>> = Record<T[number], string>;
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ var commander = require('commander');
4
+ var sync = require('../core/sync.cjs');
5
+ var logger = require('../utils/logger.cjs');
6
+
7
+ const runSync = async (options) => {
8
+ if (options.package.length === 0) {
9
+ logger.logger.error('No packages specified. Use -p <package> to specify packages.');
10
+ logger.logger.info('Example: claude-assets-sync -p @canard/schema-form');
11
+ process.exit(1);
12
+ }
13
+ if (options.dryRun)
14
+ logger.logger.dryRunNotice();
15
+ if (options.local)
16
+ logger.logger.info('[LOCAL MODE] Reading packages from workspace instead of node_modules\n');
17
+ const results = await sync.syncPackages(options.package, {
18
+ force: options.force,
19
+ dryRun: options.dryRun,
20
+ local: options.local,
21
+ ref: options.ref,
22
+ });
23
+ const hasFailures = results.some((r) => !r.success && !r.skipped);
24
+ if (hasFailures)
25
+ process.exit(1);
26
+ };
27
+ const createProgram = () => {
28
+ const program = new commander.Command();
29
+ program
30
+ .name('claude-assets-sync')
31
+ .description('Sync Claude commands and skills from npm packages to your project')
32
+ .version('0.1.0')
33
+ .option('-p, --package <name>', 'Package name to sync (can be specified multiple times)', (value, previous) => [...previous, value], [])
34
+ .option('-f, --force', 'Force sync even if version matches', false)
35
+ .option('--dry-run', 'Preview changes without writing files', false)
36
+ .option('-l, --local', 'Read packages from local workspace instead of node_modules', false)
37
+ .option('-r, --ref <ref>', 'Git ref (branch, tag, or commit) to fetch from (overrides version tag)')
38
+ .action(async (opts) => {
39
+ const options = {
40
+ package: opts.package,
41
+ force: opts.force,
42
+ dryRun: opts.dryRun,
43
+ local: opts.local,
44
+ ref: opts.ref,
45
+ };
46
+ await runSync(options);
47
+ });
48
+ return program;
49
+ };
50
+ const run = async () => {
51
+ const program = createProgram();
52
+ await program.parseAsync(process.argv);
53
+ };
54
+
55
+ exports.createProgram = createProgram;
56
+ exports.run = run;
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Create and configure the CLI program
4
+ */
5
+ export declare const createProgram: () => Command;
6
+ /**
7
+ * Run the CLI
8
+ */
9
+ export declare const run: () => Promise<void>;
@@ -0,0 +1,53 @@
1
+ import { Command } from 'commander';
2
+ import { syncPackages } from '../core/sync.mjs';
3
+ import { logger } from '../utils/logger.mjs';
4
+
5
+ const runSync = async (options) => {
6
+ if (options.package.length === 0) {
7
+ logger.error('No packages specified. Use -p <package> to specify packages.');
8
+ logger.info('Example: claude-assets-sync -p @canard/schema-form');
9
+ process.exit(1);
10
+ }
11
+ if (options.dryRun)
12
+ logger.dryRunNotice();
13
+ if (options.local)
14
+ logger.info('[LOCAL MODE] Reading packages from workspace instead of node_modules\n');
15
+ const results = await syncPackages(options.package, {
16
+ force: options.force,
17
+ dryRun: options.dryRun,
18
+ local: options.local,
19
+ ref: options.ref,
20
+ });
21
+ const hasFailures = results.some((r) => !r.success && !r.skipped);
22
+ if (hasFailures)
23
+ process.exit(1);
24
+ };
25
+ const createProgram = () => {
26
+ const program = new Command();
27
+ program
28
+ .name('claude-assets-sync')
29
+ .description('Sync Claude commands and skills from npm packages to your project')
30
+ .version('0.1.0')
31
+ .option('-p, --package <name>', 'Package name to sync (can be specified multiple times)', (value, previous) => [...previous, value], [])
32
+ .option('-f, --force', 'Force sync even if version matches', false)
33
+ .option('--dry-run', 'Preview changes without writing files', false)
34
+ .option('-l, --local', 'Read packages from local workspace instead of node_modules', false)
35
+ .option('-r, --ref <ref>', 'Git ref (branch, tag, or commit) to fetch from (overrides version tag)')
36
+ .action(async (opts) => {
37
+ const options = {
38
+ package: opts.package,
39
+ force: opts.force,
40
+ dryRun: opts.dryRun,
41
+ local: opts.local,
42
+ ref: opts.ref,
43
+ };
44
+ await runSync(options);
45
+ });
46
+ return program;
47
+ };
48
+ const run = async () => {
49
+ const program = createProgram();
50
+ await program.parseAsync(process.argv);
51
+ };
52
+
53
+ export { createProgram, run };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ var node_fs = require('node:fs');
4
+ var node_path = require('node:path');
5
+
6
+ const parsePackageName = (packageName) => {
7
+ if (packageName.startsWith('@')) {
8
+ const [scope, name] = packageName.split('/');
9
+ return [scope, name];
10
+ }
11
+ return ['', packageName];
12
+ };
13
+ const getDestinationDir = (cwd, packageName, assetType) => {
14
+ const [scope, name] = parsePackageName(packageName);
15
+ if (scope)
16
+ return node_path.join(cwd, '.claude', assetType, scope, name);
17
+ return node_path.join(cwd, '.claude', assetType, name);
18
+ };
19
+ const ensureDir = (dirPath) => {
20
+ if (!node_fs.existsSync(dirPath)) {
21
+ node_fs.mkdirSync(dirPath, { recursive: true });
22
+ }
23
+ };
24
+ const writeFile = (filePath, content) => {
25
+ ensureDir(node_path.dirname(filePath));
26
+ node_fs.writeFileSync(filePath, content, 'utf-8');
27
+ };
28
+ const readSyncMeta = (cwd, packageName, assetType) => {
29
+ try {
30
+ const destDir = getDestinationDir(cwd, packageName, assetType);
31
+ const metaPath = node_path.join(destDir, '.sync-meta.json');
32
+ const content = node_fs.readFileSync(metaPath, 'utf-8');
33
+ return JSON.parse(content);
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ };
39
+ const writeSyncMeta = (cwd, packageName, assetType, meta) => {
40
+ const destDir = getDestinationDir(cwd, packageName, assetType);
41
+ const metaPath = node_path.join(destDir, '.sync-meta.json');
42
+ writeFile(metaPath, JSON.stringify(meta, null, 2));
43
+ };
44
+ const writeAssetFile = (cwd, packageName, assetType, fileName, content) => {
45
+ const destDir = getDestinationDir(cwd, packageName, assetType);
46
+ const filePath = node_path.join(destDir, fileName);
47
+ writeFile(filePath, content);
48
+ };
49
+ const cleanAssetDir = (cwd, packageName, assetType) => {
50
+ const destDir = getDestinationDir(cwd, packageName, assetType);
51
+ if (node_fs.existsSync(destDir)) {
52
+ node_fs.rmSync(destDir, { recursive: true, force: true });
53
+ }
54
+ };
55
+ const needsSync = (cwd, packageName, version) => {
56
+ const commandsMeta = readSyncMeta(cwd, packageName, 'commands');
57
+ const skillsMeta = readSyncMeta(cwd, packageName, 'skills');
58
+ if (!commandsMeta && !skillsMeta)
59
+ return true;
60
+ if (commandsMeta && commandsMeta.version !== version)
61
+ return true;
62
+ if (skillsMeta && skillsMeta.version !== version)
63
+ return true;
64
+ return false;
65
+ };
66
+ const createSyncMeta = (version, files) => ({
67
+ version,
68
+ syncedAt: new Date().toISOString(),
69
+ files,
70
+ });
71
+
72
+ exports.cleanAssetDir = cleanAssetDir;
73
+ exports.createSyncMeta = createSyncMeta;
74
+ exports.ensureDir = ensureDir;
75
+ exports.getDestinationDir = getDestinationDir;
76
+ exports.needsSync = needsSync;
77
+ exports.parsePackageName = parsePackageName;
78
+ exports.readSyncMeta = readSyncMeta;
79
+ exports.writeAssetFile = writeAssetFile;
80
+ exports.writeFile = writeFile;
81
+ exports.writeSyncMeta = writeSyncMeta;
@@ -0,0 +1,73 @@
1
+ import type { AssetType, SyncMeta } from '../utils/types';
2
+ /**
3
+ * Parse scoped package name into scope and name
4
+ * @param packageName - Package name (e.g., "@canard/schema-form")
5
+ * @returns [scope, name] tuple (e.g., ["@canard", "schema-form"])
6
+ */
7
+ export declare const parsePackageName: (packageName: string) => [string, string];
8
+ /**
9
+ * Get the destination directory for synced assets
10
+ * @param cwd - Current working directory
11
+ * @param packageName - Package name
12
+ * @param assetType - Asset type (commands or skills)
13
+ * @returns Full path to destination directory
14
+ */
15
+ export declare const getDestinationDir: (cwd: string, packageName: string, assetType: AssetType) => string;
16
+ /**
17
+ * Ensure directory exists (creates recursively if needed)
18
+ * @param dirPath - Directory path
19
+ */
20
+ export declare const ensureDir: (dirPath: string) => void;
21
+ /**
22
+ * Write file with directory creation
23
+ * @param filePath - Full file path
24
+ * @param content - File content
25
+ */
26
+ export declare const writeFile: (filePath: string, content: string) => void;
27
+ /**
28
+ * Read sync metadata file
29
+ * @param cwd - Current working directory
30
+ * @param packageName - Package name
31
+ * @param assetType - Asset type
32
+ * @returns SyncMeta or null if not found
33
+ */
34
+ export declare const readSyncMeta: (cwd: string, packageName: string, assetType: AssetType) => SyncMeta | null;
35
+ /**
36
+ * Write sync metadata file
37
+ * @param cwd - Current working directory
38
+ * @param packageName - Package name
39
+ * @param assetType - Asset type
40
+ * @param meta - Sync metadata
41
+ */
42
+ export declare const writeSyncMeta: (cwd: string, packageName: string, assetType: AssetType, meta: SyncMeta) => void;
43
+ /**
44
+ * Write asset file to destination
45
+ * @param cwd - Current working directory
46
+ * @param packageName - Package name
47
+ * @param assetType - Asset type
48
+ * @param fileName - File name
49
+ * @param content - File content
50
+ */
51
+ export declare const writeAssetFile: (cwd: string, packageName: string, assetType: AssetType, fileName: string, content: string) => void;
52
+ /**
53
+ * Clean existing synced files for a package
54
+ * @param cwd - Current working directory
55
+ * @param packageName - Package name
56
+ * @param assetType - Asset type
57
+ */
58
+ export declare const cleanAssetDir: (cwd: string, packageName: string, assetType: AssetType) => void;
59
+ /**
60
+ * Check if package assets need sync (version mismatch)
61
+ * @param cwd - Current working directory
62
+ * @param packageName - Package name
63
+ * @param version - Current package version
64
+ * @returns true if sync is needed
65
+ */
66
+ export declare const needsSync: (cwd: string, packageName: string, version: string) => boolean;
67
+ /**
68
+ * Create SyncMeta object for current sync operation
69
+ * @param version - Package version
70
+ * @param files - List of synced file names
71
+ * @returns SyncMeta object
72
+ */
73
+ export declare const createSyncMeta: (version: string, files: string[]) => SyncMeta;
@@ -0,0 +1,70 @@
1
+ import { existsSync, rmSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+
4
+ const parsePackageName = (packageName) => {
5
+ if (packageName.startsWith('@')) {
6
+ const [scope, name] = packageName.split('/');
7
+ return [scope, name];
8
+ }
9
+ return ['', packageName];
10
+ };
11
+ const getDestinationDir = (cwd, packageName, assetType) => {
12
+ const [scope, name] = parsePackageName(packageName);
13
+ if (scope)
14
+ return join(cwd, '.claude', assetType, scope, name);
15
+ return join(cwd, '.claude', assetType, name);
16
+ };
17
+ const ensureDir = (dirPath) => {
18
+ if (!existsSync(dirPath)) {
19
+ mkdirSync(dirPath, { recursive: true });
20
+ }
21
+ };
22
+ const writeFile = (filePath, content) => {
23
+ ensureDir(dirname(filePath));
24
+ writeFileSync(filePath, content, 'utf-8');
25
+ };
26
+ const readSyncMeta = (cwd, packageName, assetType) => {
27
+ try {
28
+ const destDir = getDestinationDir(cwd, packageName, assetType);
29
+ const metaPath = join(destDir, '.sync-meta.json');
30
+ const content = readFileSync(metaPath, 'utf-8');
31
+ return JSON.parse(content);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ };
37
+ const writeSyncMeta = (cwd, packageName, assetType, meta) => {
38
+ const destDir = getDestinationDir(cwd, packageName, assetType);
39
+ const metaPath = join(destDir, '.sync-meta.json');
40
+ writeFile(metaPath, JSON.stringify(meta, null, 2));
41
+ };
42
+ const writeAssetFile = (cwd, packageName, assetType, fileName, content) => {
43
+ const destDir = getDestinationDir(cwd, packageName, assetType);
44
+ const filePath = join(destDir, fileName);
45
+ writeFile(filePath, content);
46
+ };
47
+ const cleanAssetDir = (cwd, packageName, assetType) => {
48
+ const destDir = getDestinationDir(cwd, packageName, assetType);
49
+ if (existsSync(destDir)) {
50
+ rmSync(destDir, { recursive: true, force: true });
51
+ }
52
+ };
53
+ const needsSync = (cwd, packageName, version) => {
54
+ const commandsMeta = readSyncMeta(cwd, packageName, 'commands');
55
+ const skillsMeta = readSyncMeta(cwd, packageName, 'skills');
56
+ if (!commandsMeta && !skillsMeta)
57
+ return true;
58
+ if (commandsMeta && commandsMeta.version !== version)
59
+ return true;
60
+ if (skillsMeta && skillsMeta.version !== version)
61
+ return true;
62
+ return false;
63
+ };
64
+ const createSyncMeta = (version, files) => ({
65
+ version,
66
+ syncedAt: new Date().toISOString(),
67
+ files,
68
+ });
69
+
70
+ export { cleanAssetDir, createSyncMeta, ensureDir, getDestinationDir, needsSync, parsePackageName, readSyncMeta, writeAssetFile, writeFile, writeSyncMeta };