@waypoint-framework/cli 0.2.0

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.
@@ -0,0 +1,17 @@
1
+ export interface GitExecOptions {
2
+ cwd: string;
3
+ silent?: boolean;
4
+ }
5
+ export declare function git(args: string, opts: GitExecOptions): string;
6
+ export declare function hasRemote(name: string, cwd: string): boolean;
7
+ export declare function getUpstreamUrl(cwd: string): string | undefined;
8
+ export declare function fetchUpstream(cwd: string, refspec?: string): void;
9
+ export declare function getUpstreamTags(cwd: string): string[];
10
+ export declare function commitsBehindUpstream(cwd: string, releaseBranch: string): number;
11
+ export declare function mergeUpstream(cwd: string, releaseBranch: string): {
12
+ success: boolean;
13
+ output: string;
14
+ };
15
+ export declare function abortMerge(cwd: string): void;
16
+ export declare function hasUncommittedChanges(cwd: string): boolean;
17
+ export declare function addUpstreamRemote(cwd: string, url: string): void;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.git = git;
4
+ exports.hasRemote = hasRemote;
5
+ exports.getUpstreamUrl = getUpstreamUrl;
6
+ exports.fetchUpstream = fetchUpstream;
7
+ exports.getUpstreamTags = getUpstreamTags;
8
+ exports.commitsBehindUpstream = commitsBehindUpstream;
9
+ exports.mergeUpstream = mergeUpstream;
10
+ exports.abortMerge = abortMerge;
11
+ exports.hasUncommittedChanges = hasUncommittedChanges;
12
+ exports.addUpstreamRemote = addUpstreamRemote;
13
+ const child_process_1 = require("child_process");
14
+ function git(args, opts) {
15
+ try {
16
+ return (0, child_process_1.execSync)(`git ${args}`, {
17
+ cwd: opts.cwd,
18
+ encoding: 'utf-8',
19
+ stdio: opts.silent ? ['pipe', 'pipe', 'pipe'] : undefined,
20
+ }).trim();
21
+ }
22
+ catch (err) {
23
+ if (opts.silent)
24
+ return '';
25
+ throw new Error(`git ${args} failed: ${err.stderr || err.message}`);
26
+ }
27
+ }
28
+ function hasRemote(name, cwd) {
29
+ const remotes = git('remote', { cwd, silent: true });
30
+ return remotes.split('\n').includes(name);
31
+ }
32
+ function getUpstreamUrl(cwd) {
33
+ if (!hasRemote('upstream', cwd))
34
+ return undefined;
35
+ return git('remote get-url upstream', { cwd, silent: true }) || undefined;
36
+ }
37
+ function fetchUpstream(cwd, refspec) {
38
+ const ref = refspec ? ` ${refspec}` : '';
39
+ git(`fetch upstream${ref}`, { cwd });
40
+ }
41
+ function getUpstreamTags(cwd) {
42
+ fetchUpstream(cwd, '--tags');
43
+ const output = git('tag -l "v*"', { cwd, silent: true });
44
+ if (!output)
45
+ return [];
46
+ return output.split('\n').filter(Boolean);
47
+ }
48
+ function commitsBehindUpstream(cwd, releaseBranch) {
49
+ const count = git(`rev-list HEAD..upstream/${releaseBranch} --count`, { cwd, silent: true });
50
+ return parseInt(count, 10) || 0;
51
+ }
52
+ function mergeUpstream(cwd, releaseBranch) {
53
+ try {
54
+ const output = git(`merge upstream/${releaseBranch} --no-edit`, { cwd });
55
+ return { success: true, output };
56
+ }
57
+ catch (err) {
58
+ return { success: false, output: err.message || 'Merge failed' };
59
+ }
60
+ }
61
+ function abortMerge(cwd) {
62
+ git('merge --abort', { cwd, silent: true });
63
+ }
64
+ function hasUncommittedChanges(cwd) {
65
+ const status = git('status --porcelain', { cwd, silent: true });
66
+ return status.length > 0;
67
+ }
68
+ function addUpstreamRemote(cwd, url) {
69
+ if (hasRemote('upstream', cwd)) {
70
+ git(`remote set-url upstream ${url}`, { cwd });
71
+ }
72
+ else {
73
+ git(`remote add upstream ${url}`, { cwd });
74
+ }
75
+ }
76
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/util/git.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@waypoint-framework/cli",
3
+ "version": "0.2.0",
4
+ "description": "CLI for local validation and workflow versioning in Waypoint Framework projects",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "waypoint": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "ts-node src/index.ts",
13
+ "clean": "rimraf dist"
14
+ },
15
+ "dependencies": {
16
+ "@waypoint-framework/core": "^0.1.0",
17
+ "ajv": "^8.17.1",
18
+ "commander": "^13.1.0",
19
+ "js-yaml": "^4.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/js-yaml": "^4.0.9",
23
+ "rimraf": "^5.0.5",
24
+ "ts-node": "^10.9.2",
25
+ "typescript": "^5.6.3"
26
+ }
27
+ }
@@ -0,0 +1,115 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import { VersionFile } from '@waypoint-framework/core';
4
+ import {
5
+ addUpstreamRemote,
6
+ fetchUpstream,
7
+ getUpstreamTags,
8
+ hasRemote,
9
+ getUpstreamUrl,
10
+ } from '../util/git';
11
+
12
+ export interface InitOptions {
13
+ dir: string;
14
+ workflow: string;
15
+ version: string;
16
+ tag?: string;
17
+ }
18
+
19
+ export async function init(options: InitOptions): Promise<number> {
20
+ const projectDir = path.resolve(options.dir);
21
+ const versionPath = path.join(projectDir, '.waypoint', 'version');
22
+
23
+ try {
24
+ await fs.access(versionPath);
25
+ console.error('.waypoint/version already exists.');
26
+ console.error('Use "waypoint version" to view current configuration.');
27
+ return 1;
28
+ } catch {
29
+ // expected — file doesn't exist yet
30
+ }
31
+
32
+ if (!options.workflow) {
33
+ console.error('--workflow is required (e.g. advizo-ai/marketing-workflow)');
34
+ return 1;
35
+ }
36
+
37
+ const versionLine = options.version || 'v1.x';
38
+
39
+ const lineMatch = versionLine.match(/^v?\d+\.x$/);
40
+ if (!lineMatch) {
41
+ console.error(`Invalid version line: ${versionLine}. Expected format: v1.x, v2.x, etc.`);
42
+ return 1;
43
+ }
44
+
45
+ const upstreamUrl = `https://github.com/${options.workflow}.git`;
46
+
47
+ console.log(`Configuring upstream remote → ${upstreamUrl}`);
48
+ addUpstreamRemote(projectDir, upstreamUrl);
49
+
50
+ const configuredUrl = getUpstreamUrl(projectDir);
51
+ if (configuredUrl) {
52
+ console.log(` upstream: ${configuredUrl}`);
53
+ }
54
+
55
+ let pinnedTag = options.tag;
56
+
57
+ if (!pinnedTag) {
58
+ console.log('Fetching upstream tags...');
59
+ try {
60
+ fetchUpstream(projectDir, '--tags');
61
+ const tags = getUpstreamTags(projectDir);
62
+ const latest = VersionFile.findLatestInLine(tags, versionLine);
63
+ if (latest) {
64
+ pinnedTag = latest;
65
+ console.log(` Latest tag on ${versionLine}: ${pinnedTag}`);
66
+ } else {
67
+ const major = versionLine.replace(/\.x$/, '.0.0');
68
+ pinnedTag = major.startsWith('v') ? major : `v${major}`;
69
+ console.log(` No tags found for ${versionLine}, defaulting to ${pinnedTag}`);
70
+ }
71
+ } catch {
72
+ const major = versionLine.replace(/\.x$/, '.0.0');
73
+ pinnedTag = major.startsWith('v') ? major : `v${major}`;
74
+ console.log(` Could not fetch tags, defaulting to ${pinnedTag}`);
75
+ }
76
+ }
77
+
78
+ try {
79
+ VersionFile.parseSemVer(pinnedTag);
80
+ } catch {
81
+ console.error(`Invalid tag format: ${pinnedTag}. Expected semver like v1.0.0`);
82
+ return 1;
83
+ }
84
+
85
+ const vf = VersionFile.create(
86
+ {
87
+ workflow: options.workflow,
88
+ version: versionLine,
89
+ pinned_tag: pinnedTag,
90
+ },
91
+ versionPath
92
+ );
93
+
94
+ await vf.save();
95
+
96
+ console.log('\nCreated .waypoint/version:');
97
+ console.log(` workflow: ${vf.workflow}`);
98
+ console.log(` version: ${vf.version}`);
99
+ console.log(` pinned_tag: ${vf.pinnedTag}`);
100
+
101
+ console.log('\nFetching upstream release branch...');
102
+ try {
103
+ fetchUpstream(projectDir, vf.releaseBranch);
104
+ console.log(` Fetched upstream/${vf.releaseBranch}`);
105
+ } catch {
106
+ console.log(` Branch upstream/${vf.releaseBranch} not found yet (may need to be created).`);
107
+ }
108
+
109
+ console.log('\nSetup complete. Next steps:');
110
+ console.log(' 1. git add .waypoint/version');
111
+ console.log(' 2. git commit -m "chore: initialize workflow version tracking"');
112
+ console.log(' 3. Run "waypoint version --check-upstream" to verify');
113
+
114
+ return 0;
115
+ }
@@ -0,0 +1,155 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import { VersionFile } from '@waypoint-framework/core';
4
+ import { validate } from './validate';
5
+ import {
6
+ hasRemote,
7
+ fetchUpstream,
8
+ getUpstreamTags,
9
+ commitsBehindUpstream,
10
+ mergeUpstream,
11
+ abortMerge,
12
+ hasUncommittedChanges,
13
+ } from '../util/git';
14
+
15
+ export interface UpdateOptions {
16
+ dir: string;
17
+ check: boolean;
18
+ major: boolean;
19
+ validate: boolean;
20
+ }
21
+
22
+ export async function update(options: UpdateOptions): Promise<number> {
23
+ const projectDir = path.resolve(options.dir);
24
+ const versionPath = path.join(projectDir, '.waypoint', 'version');
25
+
26
+ try {
27
+ await fs.access(versionPath);
28
+ } catch {
29
+ console.error('No .waypoint/version found. Run "waypoint init" first.');
30
+ return 1;
31
+ }
32
+
33
+ const vf = await VersionFile.fromFile(versionPath);
34
+
35
+ if (!hasRemote('upstream', projectDir)) {
36
+ console.error('No upstream remote configured.');
37
+ console.error(`Add it with: git remote add upstream https://github.com/${vf.workflow}.git`);
38
+ return 1;
39
+ }
40
+
41
+ console.log(`Fetching upstream (${vf.workflow})...`);
42
+ fetchUpstream(projectDir, '--tags');
43
+ fetchUpstream(projectDir, vf.releaseBranch);
44
+
45
+ const tags = getUpstreamTags(projectDir);
46
+ const latestInLine = VersionFile.findLatestInLine(tags, vf.version);
47
+ const latestOverall = VersionFile.findLatestTag(tags);
48
+
49
+ const hasMinorOrPatch =
50
+ latestInLine != null &&
51
+ VersionFile.compareSemVer(latestInLine, vf.pinnedTag) > 0;
52
+
53
+ const hasMajor =
54
+ latestOverall != null &&
55
+ VersionFile.parseSemVer(latestOverall).major >
56
+ VersionFile.parseSemVer(vf.pinnedTag).major;
57
+
58
+ if (!hasMinorOrPatch && !hasMajor) {
59
+ console.log(`Already up to date (${vf.pinnedTag}).`);
60
+ return 0;
61
+ }
62
+
63
+ if (hasMinorOrPatch) {
64
+ const updateType = VersionFile.classifyUpdate(vf.pinnedTag, latestInLine!);
65
+ console.log(`Update available (${updateType}): ${vf.pinnedTag} → ${latestInLine}`);
66
+
67
+ const behind = commitsBehindUpstream(projectDir, vf.releaseBranch);
68
+ console.log(`${behind} commit(s) behind upstream/${vf.releaseBranch}`);
69
+ }
70
+
71
+ if (hasMajor && !hasMinorOrPatch) {
72
+ console.log(`Major version available: ${latestOverall}`);
73
+ if (!options.major) {
74
+ console.log('Use --major to upgrade to the next major version.');
75
+ return 0;
76
+ }
77
+ }
78
+
79
+ if (hasMajor && options.major) {
80
+ console.log(`\n⚠ Major update available: ${latestOverall}`);
81
+ console.log('Major updates may contain breaking schema changes.');
82
+ }
83
+
84
+ if (options.check) {
85
+ console.log('\n(check-only mode, no changes applied)');
86
+ return 0;
87
+ }
88
+
89
+ if (hasUncommittedChanges(projectDir)) {
90
+ console.error('\nUncommitted changes detected. Commit or stash them before updating.');
91
+ return 1;
92
+ }
93
+
94
+ let targetBranch: string;
95
+ let targetTag: string;
96
+
97
+ if (options.major && hasMajor) {
98
+ const newMajor = VersionFile.parseSemVer(latestOverall!).major;
99
+ targetBranch = `release/v${newMajor}.x`;
100
+ targetTag = latestOverall!;
101
+
102
+ console.log(`\nFetching major release branch: ${targetBranch}`);
103
+ fetchUpstream(projectDir, targetBranch);
104
+ } else if (hasMinorOrPatch) {
105
+ targetBranch = vf.releaseBranch;
106
+ targetTag = latestInLine!;
107
+ } else {
108
+ console.log('No applicable updates.');
109
+ return 0;
110
+ }
111
+
112
+ console.log(`\nMerging upstream/${targetBranch}...`);
113
+ const result = mergeUpstream(projectDir, targetBranch);
114
+
115
+ if (!result.success) {
116
+ console.error('\nMerge conflicts detected!');
117
+ console.error('Resolve conflicts manually, then run:');
118
+ console.error(' git add . && git commit');
119
+ console.error(' waypoint validate --local --all');
120
+
121
+ abortMerge(projectDir);
122
+ return 1;
123
+ }
124
+
125
+ console.log('Merge successful.');
126
+
127
+ const newVersion = options.major && hasMajor
128
+ ? `v${VersionFile.parseSemVer(targetTag).major}.x`
129
+ : vf.version;
130
+
131
+ const updatedVf = VersionFile.create(
132
+ {
133
+ workflow: vf.workflow,
134
+ version: newVersion,
135
+ pinned_tag: targetTag,
136
+ },
137
+ versionPath
138
+ );
139
+ await updatedVf.save();
140
+ console.log(`Updated .waypoint/version: pinned_tag → ${targetTag}`);
141
+
142
+ if (options.validate) {
143
+ console.log('\nRunning post-update validation...');
144
+ const exitCode = await validate({ local: true, all: true, dir: projectDir });
145
+ if (exitCode !== 0) {
146
+ console.error('\nValidation failed after update.');
147
+ console.error('Fix validation errors, then commit.');
148
+ return 1;
149
+ }
150
+ console.log('All validations passed.');
151
+ }
152
+
153
+ console.log('\nUpdate complete. Review changes and commit when ready.');
154
+ return 0;
155
+ }
@@ -0,0 +1,117 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { execSync } from 'child_process';
4
+ import Ajv from 'ajv';
5
+ import { WaypointYaml } from '@waypoint-framework/core';
6
+
7
+ export interface ValidateOptions {
8
+ local: boolean;
9
+ all: boolean;
10
+ dir: string;
11
+ }
12
+
13
+ export async function validate(options: ValidateOptions): Promise<number> {
14
+ const projectDir = path.resolve(options.dir);
15
+ const waypointPath = path.join(projectDir, 'waypoint.yaml');
16
+
17
+ try {
18
+ await fs.access(waypointPath);
19
+ } catch {
20
+ console.error(`No waypoint.yaml found in ${projectDir}`);
21
+ return 1;
22
+ }
23
+
24
+ const wp = await WaypointYaml.fromFile(waypointPath);
25
+ const schemaMappings = wp.getSchemaMappings(path.join(projectDir, 'schemas'));
26
+
27
+ let filesToValidate: string[];
28
+
29
+ if (options.all) {
30
+ filesToValidate = getAllArtifactFiles(wp, projectDir);
31
+ } else {
32
+ filesToValidate = getChangedFiles(projectDir);
33
+ }
34
+
35
+ if (filesToValidate.length === 0) {
36
+ console.log('No files to validate.');
37
+ return 0;
38
+ }
39
+
40
+ const ajv = new Ajv({ allErrors: true, strict: false });
41
+ let hasErrors = false;
42
+
43
+ for (const filePath of filesToValidate) {
44
+ const waypointId = wp.findWaypointForFile(filePath);
45
+ if (!waypointId) {
46
+ continue;
47
+ }
48
+
49
+ const schemaMapping = schemaMappings.find((m: { waypointId: string }) => m.waypointId === waypointId);
50
+ if (!schemaMapping) {
51
+ continue;
52
+ }
53
+
54
+ let schema: any;
55
+ try {
56
+ const schemaContent = await fs.readFile(schemaMapping.schemaPath, 'utf-8');
57
+ schema = JSON.parse(schemaContent);
58
+ } catch {
59
+ console.warn(` Schema not found: ${schemaMapping.schemaPath}`);
60
+ continue;
61
+ }
62
+
63
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
64
+ let data: any;
65
+ try {
66
+ const content = await fs.readFile(fullPath, 'utf-8');
67
+ data = JSON.parse(content);
68
+ } catch {
69
+ continue;
70
+ }
71
+
72
+ const valid = ajv.validate(schema, data);
73
+ if (!valid) {
74
+ hasErrors = true;
75
+ console.error(`FAIL ${filePath}`);
76
+ for (const err of ajv.errors || []) {
77
+ console.error(` ${err.instancePath || '/'}: ${err.message}`);
78
+ }
79
+ } else {
80
+ console.log(`PASS ${filePath}`);
81
+ }
82
+ }
83
+
84
+ return hasErrors ? 1 : 0;
85
+ }
86
+
87
+ function getChangedFiles(projectDir: string): string[] {
88
+ try {
89
+ const staged = execSync('git diff --cached --name-only', {
90
+ cwd: projectDir,
91
+ encoding: 'utf-8',
92
+ }).trim();
93
+
94
+ if (staged) {
95
+ return staged.split('\n').filter(Boolean);
96
+ }
97
+
98
+ const unstaged = execSync('git diff --name-only', {
99
+ cwd: projectDir,
100
+ encoding: 'utf-8',
101
+ }).trim();
102
+
103
+ return unstaged.split('\n').filter(Boolean);
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ function getAllArtifactFiles(wp: WaypointYaml, projectDir: string): string[] {
110
+ const allIds = wp.getAllNodeIds();
111
+ const files: string[] = [];
112
+ for (const id of allIds) {
113
+ files.push(`${id}.json`);
114
+ files.push(`${id}.md`);
115
+ }
116
+ return files;
117
+ }
@@ -0,0 +1,90 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import { VersionFile } from '@waypoint-framework/core';
4
+ import {
5
+ getUpstreamUrl,
6
+ getUpstreamTags,
7
+ commitsBehindUpstream,
8
+ hasRemote,
9
+ } from '../util/git';
10
+
11
+ export interface VersionOptions {
12
+ dir: string;
13
+ checkUpstream: boolean;
14
+ }
15
+
16
+ export async function version(options: VersionOptions): Promise<number> {
17
+ const projectDir = path.resolve(options.dir);
18
+ const versionPath = path.join(projectDir, '.waypoint', 'version');
19
+
20
+ try {
21
+ await fs.access(versionPath);
22
+ } catch {
23
+ console.error(`No .waypoint/version found in ${projectDir}`);
24
+ console.error('Run "waypoint init" to set up workflow version tracking.');
25
+ return 1;
26
+ }
27
+
28
+ const vf = await VersionFile.fromFile(versionPath);
29
+
30
+ console.log('Workflow Version Info');
31
+ console.log('────────────────────');
32
+ console.log(` Workflow: ${vf.workflow}`);
33
+ console.log(` Version: ${vf.version}`);
34
+ console.log(` Pinned Tag: ${vf.pinnedTag}`);
35
+ console.log(` Release Branch: ${vf.releaseBranch}`);
36
+
37
+ const upstreamUrl = getUpstreamUrl(projectDir);
38
+ if (upstreamUrl) {
39
+ console.log(` Upstream: ${upstreamUrl}`);
40
+ } else {
41
+ console.log(' Upstream: (not configured)');
42
+ }
43
+
44
+ if (!options.checkUpstream) return 0;
45
+
46
+ if (!hasRemote('upstream', projectDir)) {
47
+ console.log('\n⚠ No upstream remote configured. Cannot check for updates.');
48
+ return 0;
49
+ }
50
+
51
+ console.log('\nChecking upstream for updates...');
52
+
53
+ try {
54
+ const tags = getUpstreamTags(projectDir);
55
+ if (tags.length === 0) {
56
+ console.log(' No version tags found on upstream.');
57
+ return 0;
58
+ }
59
+
60
+ const latestInLine = VersionFile.findLatestInLine(tags, vf.version);
61
+ const latestOverall = VersionFile.findLatestTag(tags);
62
+
63
+ if (latestInLine && VersionFile.compareSemVer(latestInLine, vf.pinnedTag) > 0) {
64
+ const updateType = VersionFile.classifyUpdate(vf.pinnedTag, latestInLine);
65
+ console.log(` Update available (${updateType}): ${vf.pinnedTag} → ${latestInLine}`);
66
+
67
+ const behind = commitsBehindUpstream(projectDir, vf.releaseBranch);
68
+ if (behind > 0) {
69
+ console.log(` ${behind} commit(s) behind upstream/${vf.releaseBranch}`);
70
+ }
71
+ console.log(' Run "waypoint update" to apply.');
72
+ } else {
73
+ console.log(` Up to date on ${vf.version} line (${vf.pinnedTag}).`);
74
+ }
75
+
76
+ if (latestOverall) {
77
+ const latestSv = VersionFile.parseSemVer(latestOverall);
78
+ const pinnedSv = VersionFile.parseSemVer(vf.pinnedTag);
79
+ if (latestSv.major > pinnedSv.major) {
80
+ console.log(`\n⚠ Major version available: ${latestOverall}`);
81
+ console.log(' Major updates may contain breaking schema changes.');
82
+ console.log(' Run "waypoint update --major" to migrate.');
83
+ }
84
+ }
85
+ } catch (err: any) {
86
+ console.error(`\nFailed to check upstream: ${err.message}`);
87
+ }
88
+
89
+ return 0;
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { validate } from './commands/validate';
4
+ import { version } from './commands/version';
5
+ import { update } from './commands/update';
6
+ import { init } from './commands/init';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('waypoint')
12
+ .description('Waypoint Framework CLI — local validation and workflow versioning for project repos')
13
+ .version('0.2.0');
14
+
15
+ program
16
+ .command('validate')
17
+ .description('Validate changed files against their waypoint JSON schemas')
18
+ .option('--local', 'Run validation locally (default)', true)
19
+ .option('--all', 'Validate all files, not just changed ones')
20
+ .option('--dir <path>', 'Project directory (defaults to cwd)', process.cwd())
21
+ .action(async (options) => {
22
+ try {
23
+ const exitCode = await validate(options);
24
+ process.exit(exitCode);
25
+ } catch (err: any) {
26
+ console.error(`Error: ${err.message}`);
27
+ process.exit(1);
28
+ }
29
+ });
30
+
31
+ program
32
+ .command('version')
33
+ .description('Show workflow version info for this project')
34
+ .option('--dir <path>', 'Project directory (defaults to cwd)', process.cwd())
35
+ .option('--check-upstream', 'Check upstream repo for available updates', false)
36
+ .action(async (options) => {
37
+ try {
38
+ const exitCode = await version(options);
39
+ process.exit(exitCode);
40
+ } catch (err: any) {
41
+ console.error(`Error: ${err.message}`);
42
+ process.exit(1);
43
+ }
44
+ });
45
+
46
+ program
47
+ .command('update')
48
+ .description('Check for and apply workflow updates from the upstream repo')
49
+ .option('--dir <path>', 'Project directory (defaults to cwd)', process.cwd())
50
+ .option('--check', 'Only check for updates, do not apply', false)
51
+ .option('--major', 'Allow major version upgrades (breaking changes)', false)
52
+ .option('--no-validate', 'Skip post-update validation')
53
+ .action(async (options) => {
54
+ try {
55
+ const exitCode = await update(options);
56
+ process.exit(exitCode);
57
+ } catch (err: any) {
58
+ console.error(`Error: ${err.message}`);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ program
64
+ .command('init')
65
+ .description('Initialize workflow version tracking for a project fork')
66
+ .requiredOption('--workflow <owner/repo>', 'Upstream workflow repo (e.g. advizo-ai/marketing-workflow)')
67
+ .option('--version <line>', 'Version line to track (e.g. v1.x)', 'v1.x')
68
+ .option('--tag <tag>', 'Specific tag to pin (auto-detected if omitted)')
69
+ .option('--dir <path>', 'Project directory (defaults to cwd)', process.cwd())
70
+ .action(async (options) => {
71
+ try {
72
+ const exitCode = await init(options);
73
+ process.exit(exitCode);
74
+ } catch (err: any) {
75
+ console.error(`Error: ${err.message}`);
76
+ process.exit(1);
77
+ }
78
+ });
79
+
80
+ program.parse();