@train860/harness 0.1.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.
- package/bin/harness +185 -0
- package/package.json +15 -0
package/bin/harness
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import childProcess from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SOURCE_REPO = 'train860/harness';
|
|
10
|
+
const DEFAULT_DISTRIBUTION = 'npm-public-github-private';
|
|
11
|
+
|
|
12
|
+
function readArgs(argv) {
|
|
13
|
+
const positional = [];
|
|
14
|
+
const options = {};
|
|
15
|
+
|
|
16
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
17
|
+
const key = argv[index];
|
|
18
|
+
if (!key.startsWith('--')) {
|
|
19
|
+
positional.push(key);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
options[key.slice(2)] = argv[index + 1];
|
|
24
|
+
index += 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { positional, options };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function run(command, args, options = {}) {
|
|
31
|
+
const result = childProcess.spawnSync(command, args, {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
...options,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (result.stdout) {
|
|
37
|
+
process.stdout.write(result.stdout);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (result.stderr) {
|
|
41
|
+
process.stderr.write(result.stderr);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (result.status !== 0) {
|
|
45
|
+
process.exit(result.status ?? 1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function requireValue(value, message) {
|
|
50
|
+
if (value) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
console.error(message);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureSourceRepo(options, cwd) {
|
|
58
|
+
if (options['source-repo']) {
|
|
59
|
+
return path.resolve(options['source-repo']);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const repo = options.repo ?? DEFAULT_SOURCE_REPO;
|
|
63
|
+
const ref = options.ref ?? 'main';
|
|
64
|
+
const cacheDir = options['cache-dir']
|
|
65
|
+
? path.resolve(options['cache-dir'])
|
|
66
|
+
: path.join(os.tmpdir(), 'harness-cache', repo.replace(/[\\/]/g, '-'));
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(path.join(cacheDir, '.git'))) {
|
|
69
|
+
run('git', ['-C', cacheDir, 'fetch', '--tags', 'origin'], { cwd });
|
|
70
|
+
} else {
|
|
71
|
+
fs.mkdirSync(path.dirname(cacheDir), { recursive: true });
|
|
72
|
+
run('gh', ['repo', 'clone', repo, cacheDir], { cwd });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
run('git', ['-C', cacheDir, 'checkout', ref], { cwd });
|
|
76
|
+
return cacheDir;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readSourceVersion(sourceRepo) {
|
|
80
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(sourceRepo, 'package.json'), 'utf8'));
|
|
81
|
+
return packageJson.version ?? '0.1.0-dev';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readSourceCommit(sourceRepo) {
|
|
85
|
+
const result = childProcess.spawnSync('git', ['-C', sourceRepo, 'rev-parse', 'HEAD'], {
|
|
86
|
+
encoding: 'utf8',
|
|
87
|
+
});
|
|
88
|
+
return result.status === 0 ? result.stdout.trim() : 'unknown';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function usage() {
|
|
92
|
+
console.error([
|
|
93
|
+
'Usage:',
|
|
94
|
+
' harness init [target-dir] --package-scope <scope> --go-module-prefix <prefix> [--project-name <name>] [--repo <org/repo> | --source-repo <path>] [--ref <git-ref>] [--cache-dir <dir>] [--app-names-file <json-file>]',
|
|
95
|
+
' harness upgrade [--project-root <dir>] [--repo <org/repo> | --source-repo <path>] [--ref <git-ref>] [--cache-dir <dir>]',
|
|
96
|
+
'',
|
|
97
|
+
`Default GitHub source repo: ${DEFAULT_SOURCE_REPO}`,
|
|
98
|
+
].join('\n'));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function commandInit(argv) {
|
|
103
|
+
const cwd = process.cwd();
|
|
104
|
+
const { positional, options } = readArgs(argv);
|
|
105
|
+
const targetDir = options['target-dir'] ?? positional[0];
|
|
106
|
+
const resolvedTargetDir = requireValue(
|
|
107
|
+
targetDir ? path.resolve(targetDir) : null,
|
|
108
|
+
'Usage error: missing target directory for `harness init`.',
|
|
109
|
+
);
|
|
110
|
+
requireValue(options['package-scope'], 'Usage error: missing --package-scope for `harness init`.');
|
|
111
|
+
requireValue(options['go-module-prefix'], 'Usage error: missing --go-module-prefix for `harness init`.');
|
|
112
|
+
|
|
113
|
+
const sourceRepo = ensureSourceRepo(options, cwd);
|
|
114
|
+
const projectName = options['project-name'] ?? path.basename(resolvedTargetDir);
|
|
115
|
+
const createArgs = [
|
|
116
|
+
path.join(sourceRepo, 'packages/create-ai-native-scaffold/bin/create-ai-native-scaffold.mjs'),
|
|
117
|
+
'--project-name',
|
|
118
|
+
projectName,
|
|
119
|
+
'--package-scope',
|
|
120
|
+
options['package-scope'],
|
|
121
|
+
'--go-module-prefix',
|
|
122
|
+
options['go-module-prefix'],
|
|
123
|
+
'--target-dir',
|
|
124
|
+
resolvedTargetDir,
|
|
125
|
+
'--scaffold-repo',
|
|
126
|
+
options.repo ?? DEFAULT_SOURCE_REPO,
|
|
127
|
+
'--scaffold-version',
|
|
128
|
+
readSourceVersion(sourceRepo),
|
|
129
|
+
'--scaffold-commit',
|
|
130
|
+
readSourceCommit(sourceRepo),
|
|
131
|
+
'--scaffold-distribution',
|
|
132
|
+
DEFAULT_DISTRIBUTION,
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
if (options['app-names-file']) {
|
|
136
|
+
createArgs.push('--app-names-file', path.resolve(options['app-names-file']));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
run(process.execPath, createArgs, { cwd: sourceRepo });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function commandUpgrade(argv) {
|
|
143
|
+
const cwd = process.cwd();
|
|
144
|
+
const { options } = readArgs(argv);
|
|
145
|
+
const projectRoot = path.resolve(options['project-root'] ?? cwd);
|
|
146
|
+
const upgradeScript = path.join(projectRoot, 'scripts/scaffold/upgrade.mjs');
|
|
147
|
+
|
|
148
|
+
requireValue(fs.existsSync(upgradeScript) ? upgradeScript : null, [
|
|
149
|
+
'Usage error: missing generated scaffold upgrade script.',
|
|
150
|
+
'Run `harness upgrade --project-root <generated-project>` from a scaffolded repo or pass --project-root.',
|
|
151
|
+
].join('\n'));
|
|
152
|
+
|
|
153
|
+
const forwardedArgs = [];
|
|
154
|
+
|
|
155
|
+
if (options['source-repo']) {
|
|
156
|
+
forwardedArgs.push('--source-repo', path.resolve(options['source-repo']));
|
|
157
|
+
} else {
|
|
158
|
+
forwardedArgs.push('--repo', options.repo ?? DEFAULT_SOURCE_REPO);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (options.ref) {
|
|
162
|
+
forwardedArgs.push('--ref', options.ref);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options['cache-dir']) {
|
|
166
|
+
forwardedArgs.push('--cache-dir', path.resolve(options['cache-dir']));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
run(process.execPath, [upgradeScript, ...forwardedArgs], { cwd: projectRoot });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
173
|
+
|
|
174
|
+
if (!command || command === '--help' || command === '-h') {
|
|
175
|
+
usage();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (command === 'init') {
|
|
179
|
+
commandInit(rest);
|
|
180
|
+
} else if (command === 'upgrade') {
|
|
181
|
+
commandUpgrade(rest);
|
|
182
|
+
} else {
|
|
183
|
+
console.error(`Unknown command: ${command}`);
|
|
184
|
+
usage();
|
|
185
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@train860/harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Public bootstrap CLI for the private AI-native scaffold source repo.",
|
|
6
|
+
"directories": {
|
|
7
|
+
"bin": "./bin"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin"
|
|
14
|
+
]
|
|
15
|
+
}
|