@duypham93/openkit 0.2.5 → 0.2.7
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/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
function removePathIfPresent(targetPath) {
|
|
5
|
+
try {
|
|
6
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
7
|
+
} catch {
|
|
8
|
+
// ignore cleanup failures
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
function ensureParent(filePath) {
|
|
5
13
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
6
14
|
}
|
|
@@ -13,6 +21,10 @@ function writeFile(filePath, content, mode) {
|
|
|
13
21
|
}
|
|
14
22
|
}
|
|
15
23
|
|
|
24
|
+
function writeJson(filePath, value) {
|
|
25
|
+
writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
function relativeTarget(fromPath, toPath) {
|
|
17
29
|
return path.relative(path.dirname(fromPath), toPath) || '.';
|
|
18
30
|
}
|
|
@@ -22,7 +34,7 @@ function createSymlinkOrCopy({ linkPath, targetPath, type = 'file' }) {
|
|
|
22
34
|
|
|
23
35
|
try {
|
|
24
36
|
if (fs.existsSync(linkPath) || fs.lstatSync(linkPath)) {
|
|
25
|
-
|
|
37
|
+
removePathIfPresent(linkPath);
|
|
26
38
|
}
|
|
27
39
|
} catch {
|
|
28
40
|
// ignore cleanup misses
|
|
@@ -42,28 +54,40 @@ function createSymlinkOrCopy({ linkPath, targetPath, type = 'file' }) {
|
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
56
|
|
|
57
|
+
function createIfMissing(createdPaths, { linkPath, targetPath, type = 'file' }) {
|
|
58
|
+
if (fs.existsSync(linkPath)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const mode = createSymlinkOrCopy({ linkPath, targetPath, type });
|
|
63
|
+
createdPaths.push(linkPath);
|
|
64
|
+
return mode;
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
export function ensureWorkspaceShim(paths) {
|
|
68
|
+
const createdPaths = [];
|
|
69
|
+
|
|
46
70
|
fs.mkdirSync(paths.workspaceShimDir, { recursive: true });
|
|
47
71
|
|
|
48
|
-
|
|
72
|
+
createIfMissing(createdPaths, {
|
|
49
73
|
linkPath: paths.workspaceShimAgentsPath,
|
|
50
74
|
targetPath: path.join(paths.kitRoot, 'AGENTS.md'),
|
|
51
75
|
type: 'file',
|
|
52
76
|
});
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
createIfMissing(createdPaths, {
|
|
55
79
|
linkPath: paths.workspaceShimContextDir,
|
|
56
80
|
targetPath: path.join(paths.kitRoot, 'context'),
|
|
57
81
|
type: 'dir',
|
|
58
82
|
});
|
|
59
83
|
|
|
60
|
-
|
|
84
|
+
createIfMissing(createdPaths, {
|
|
61
85
|
linkPath: paths.workspaceShimTemplatesDir,
|
|
62
86
|
targetPath: path.join(paths.kitRoot, 'docs', 'templates'),
|
|
63
87
|
type: 'dir',
|
|
64
88
|
});
|
|
65
89
|
|
|
66
|
-
|
|
90
|
+
createIfMissing(createdPaths, {
|
|
67
91
|
linkPath: paths.workspaceShimWorkflowStatePath,
|
|
68
92
|
targetPath: paths.workflowStatePath,
|
|
69
93
|
type: 'file',
|
|
@@ -85,20 +109,97 @@ if (result.error) {
|
|
|
85
109
|
process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
86
110
|
`;
|
|
87
111
|
|
|
88
|
-
|
|
112
|
+
if (!fs.existsSync(paths.workspaceShimWorkflowCliPath)) {
|
|
113
|
+
writeFile(paths.workspaceShimWorkflowCliPath, workflowCli, 0o755);
|
|
114
|
+
createdPaths.push(paths.workspaceShimWorkflowCliPath);
|
|
115
|
+
}
|
|
89
116
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
createIfMissing(createdPaths, {
|
|
118
|
+
linkPath: path.join(paths.projectRoot, 'AGENTS.md'),
|
|
119
|
+
targetPath: paths.workspaceShimAgentsPath,
|
|
120
|
+
type: 'file',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
createIfMissing(createdPaths, {
|
|
124
|
+
linkPath: path.join(paths.projectRoot, 'context'),
|
|
125
|
+
targetPath: paths.workspaceShimContextDir,
|
|
126
|
+
type: 'dir',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
createIfMissing(createdPaths, {
|
|
130
|
+
linkPath: path.join(paths.projectRoot, '.opencode', 'workflow-state.json'),
|
|
131
|
+
targetPath: paths.workspaceShimWorkflowStatePath,
|
|
132
|
+
type: 'file',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const opencodeRoot = path.join(paths.projectRoot, '.opencode');
|
|
136
|
+
const opencodePackagePath = path.join(opencodeRoot, 'package.json');
|
|
137
|
+
if (!fs.existsSync(opencodePackagePath)) {
|
|
138
|
+
writeJson(opencodePackagePath, { type: 'module' });
|
|
139
|
+
createdPaths.push(opencodePackagePath);
|
|
101
140
|
}
|
|
102
141
|
|
|
103
|
-
|
|
142
|
+
if (!fs.existsSync(path.join(paths.projectRoot, '.opencode', 'workflow-state.js'))) {
|
|
143
|
+
const rootWorkflowCli = `#!/usr/bin/env node
|
|
144
|
+
import { spawnSync } from 'node:child_process';
|
|
145
|
+
|
|
146
|
+
const rawArgs = process.argv.slice(2);
|
|
147
|
+
const command = rawArgs[0];
|
|
148
|
+
const aliasMap = new Map([
|
|
149
|
+
['get', 'show'],
|
|
150
|
+
['--help', 'help'],
|
|
151
|
+
['-h', 'help'],
|
|
152
|
+
]);
|
|
153
|
+
const normalizedArgs = rawArgs.length === 0 ? ['help'] : [aliasMap.get(command) ?? command, ...rawArgs.slice(1)];
|
|
154
|
+
const result = spawnSync(process.execPath, [${JSON.stringify(paths.workspaceShimWorkflowCliPath)}, ...normalizedArgs], {
|
|
155
|
+
stdio: 'inherit',
|
|
156
|
+
env: process.env,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (result.error) {
|
|
160
|
+
throw result.error;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const rootWorkflowCliPath = path.join(paths.projectRoot, '.opencode', 'workflow-state.js');
|
|
167
|
+
writeFile(rootWorkflowCliPath, rootWorkflowCli, 0o755);
|
|
168
|
+
createdPaths.push(rootWorkflowCliPath);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!fs.existsSync(path.join(paths.projectRoot, '.opencode', 'work-items'))) {
|
|
172
|
+
createIfMissing(createdPaths, {
|
|
173
|
+
linkPath: path.join(paths.projectRoot, '.opencode', 'work-items'),
|
|
174
|
+
targetPath: paths.workItemsDir,
|
|
175
|
+
type: 'dir',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
paths,
|
|
181
|
+
createdPaths,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function cleanupWorkspaceShim(shim) {
|
|
186
|
+
if (!shim?.createdPaths) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const createdPath of [...shim.createdPaths].reverse()) {
|
|
191
|
+
removePathIfPresent(createdPath);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const maybeShimDir = shim.paths?.workspaceShimDir;
|
|
195
|
+
if (maybeShimDir && fs.existsSync(maybeShimDir)) {
|
|
196
|
+
try {
|
|
197
|
+
const entries = fs.readdirSync(maybeShimDir);
|
|
198
|
+
if (entries.length === 0) {
|
|
199
|
+
removePathIfPresent(maybeShimDir);
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// ignore cleanup misses
|
|
203
|
+
}
|
|
204
|
+
}
|
|
104
205
|
}
|
|
@@ -51,9 +51,12 @@ export function ensureWorkspaceBootstrap(options = {}) {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
ensureWorkspaceShim(paths);
|
|
54
|
+
const shim = ensureWorkspaceShim(paths);
|
|
55
55
|
|
|
56
|
-
return
|
|
56
|
+
return {
|
|
57
|
+
...paths,
|
|
58
|
+
workspaceShim: shim,
|
|
59
|
+
};
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export function readWorkspaceMeta(options = {}) {
|
|
@@ -33,6 +33,10 @@ function writeExecutable(filePath, content) {
|
|
|
33
33
|
fs.chmodSync(filePath, 0o755);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function removePathIfPresent(targetPath) {
|
|
37
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
test('openkit --help shows global-install oriented help', () => {
|
|
37
41
|
const result = runCli(['--help']);
|
|
38
42
|
|
|
@@ -226,6 +230,10 @@ process.stdout.write('mock opencode launched\\n');
|
|
|
226
230
|
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'context', 'core', 'workflow.md')), true);
|
|
227
231
|
assert.equal(fs.lstatSync(path.join(projectRoot, '.opencode', 'openkit', 'workflow-state.json')).isSymbolicLink() || fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'workflow-state.json')), true);
|
|
228
232
|
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'workflow-state.js')), true);
|
|
233
|
+
assert.equal(fs.existsSync(path.join(projectRoot, 'AGENTS.md')), true);
|
|
234
|
+
assert.equal(fs.existsSync(path.join(projectRoot, 'context', 'core', 'workflow.md')), true);
|
|
235
|
+
assert.equal(fs.lstatSync(path.join(projectRoot, '.opencode', 'workflow-state.json')).isSymbolicLink() || fs.existsSync(path.join(projectRoot, '.opencode', 'workflow-state.json')), true);
|
|
236
|
+
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'workflow-state.js')), true);
|
|
229
237
|
});
|
|
230
238
|
|
|
231
239
|
test('openkit run does not reinstall when the global install already exists', () => {
|
|
@@ -301,6 +309,88 @@ process.stdout.write('mock opencode launched after auto-install\\n');
|
|
|
301
309
|
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'AGENTS.md')), true);
|
|
302
310
|
});
|
|
303
311
|
|
|
312
|
+
test('openkit run does not overwrite existing repo-local workflow files when creating shims', () => {
|
|
313
|
+
const tempHome = makeTempDir();
|
|
314
|
+
const projectRoot = makeTempDir();
|
|
315
|
+
const fakeBinDir = path.join(tempHome, 'bin');
|
|
316
|
+
|
|
317
|
+
fs.mkdirSync(path.join(projectRoot, '.opencode'), { recursive: true });
|
|
318
|
+
fs.mkdirSync(path.join(projectRoot, 'context', 'core'), { recursive: true });
|
|
319
|
+
fs.writeFileSync(path.join(projectRoot, 'AGENTS.md'), 'project agents\n', 'utf8');
|
|
320
|
+
fs.writeFileSync(path.join(projectRoot, 'context', 'core', 'workflow.md'), 'project workflow\n', 'utf8');
|
|
321
|
+
fs.writeFileSync(path.join(projectRoot, '.opencode', 'workflow-state.json'), '{"project":true}\n', 'utf8');
|
|
322
|
+
fs.writeFileSync(path.join(projectRoot, '.opencode', 'workflow-state.js'), '#!/usr/bin/env node\n', 'utf8');
|
|
323
|
+
|
|
324
|
+
writeExecutable(path.join(fakeBinDir, 'opencode'), '#!/bin/sh\nexit 0\n');
|
|
325
|
+
|
|
326
|
+
const result = runCli(['run'], {
|
|
327
|
+
cwd: projectRoot,
|
|
328
|
+
env: {
|
|
329
|
+
...process.env,
|
|
330
|
+
OPENCODE_HOME: tempHome,
|
|
331
|
+
PATH: `${fakeBinDir}${path.delimiter}${process.env.PATH}`,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
assert.equal(result.status, 0);
|
|
336
|
+
assert.equal(fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'), 'project agents\n');
|
|
337
|
+
assert.equal(fs.readFileSync(path.join(projectRoot, 'context', 'core', 'workflow.md'), 'utf8'), 'project workflow\n');
|
|
338
|
+
assert.equal(fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.json'), 'utf8'), '{"project":true}\n');
|
|
339
|
+
assert.equal(fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.js'), 'utf8'), '#!/usr/bin/env node\n');
|
|
340
|
+
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'AGENTS.md')), true);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('openkit run cleans root compatibility shims when created files are removed', () => {
|
|
344
|
+
const tempHome = makeTempDir();
|
|
345
|
+
const projectRoot = makeTempDir();
|
|
346
|
+
const fakeBinDir = path.join(tempHome, 'bin');
|
|
347
|
+
|
|
348
|
+
writeExecutable(path.join(fakeBinDir, 'opencode'), '#!/bin/sh\nexit 0\n');
|
|
349
|
+
|
|
350
|
+
const result = runCli(['run'], {
|
|
351
|
+
cwd: projectRoot,
|
|
352
|
+
env: {
|
|
353
|
+
...process.env,
|
|
354
|
+
OPENCODE_HOME: tempHome,
|
|
355
|
+
PATH: `${fakeBinDir}${path.delimiter}${process.env.PATH}`,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
assert.equal(result.status, 0);
|
|
360
|
+
assert.equal(fs.existsSync(path.join(projectRoot, 'AGENTS.md')), true);
|
|
361
|
+
|
|
362
|
+
removePathIfPresent(path.join(projectRoot, 'AGENTS.md'));
|
|
363
|
+
removePathIfPresent(path.join(projectRoot, 'context'));
|
|
364
|
+
removePathIfPresent(path.join(projectRoot, '.opencode', 'workflow-state.json'));
|
|
365
|
+
removePathIfPresent(path.join(projectRoot, '.opencode', 'workflow-state.js'));
|
|
366
|
+
|
|
367
|
+
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'AGENTS.md')), true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('openkit run creates a module-aware root workflow wrapper with alias support', () => {
|
|
371
|
+
const tempHome = makeTempDir();
|
|
372
|
+
const projectRoot = makeTempDir();
|
|
373
|
+
const fakeBinDir = path.join(tempHome, 'bin');
|
|
374
|
+
|
|
375
|
+
writeExecutable(path.join(fakeBinDir, 'opencode'), '#!/bin/sh\nexit 0\n');
|
|
376
|
+
|
|
377
|
+
const result = runCli(['run'], {
|
|
378
|
+
cwd: projectRoot,
|
|
379
|
+
env: {
|
|
380
|
+
...process.env,
|
|
381
|
+
OPENCODE_HOME: tempHome,
|
|
382
|
+
PATH: `${fakeBinDir}${path.delimiter}${process.env.PATH}`,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
assert.equal(result.status, 0);
|
|
387
|
+
assert.deepEqual(readJson(path.join(projectRoot, '.opencode', 'package.json')), { type: 'module' });
|
|
388
|
+
|
|
389
|
+
const wrapper = fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.js'), 'utf8');
|
|
390
|
+
assert.match(wrapper, /\['get', 'show'\]/);
|
|
391
|
+
assert.match(wrapper, /\['--help', 'help'\]/);
|
|
392
|
+
});
|
|
393
|
+
|
|
304
394
|
test('openkit run reports missing opencode after first-time setup completes', () => {
|
|
305
395
|
const tempHome = makeTempDir();
|
|
306
396
|
const projectRoot = makeTempDir();
|