agentic-workflow-manager 2.0.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/README.md +62 -0
- package/dist/src/commands/doctor.js +77 -0
- package/dist/src/commands/hooks/index.js +118 -0
- package/dist/src/commands/hooks/install.js +113 -0
- package/dist/src/commands/hooks/resync.js +50 -0
- package/dist/src/commands/hooks/status.js +74 -0
- package/dist/src/commands/hooks/uninstall.js +59 -0
- package/dist/src/commands/init.js +181 -0
- package/dist/src/commands/ledger/index.js +73 -0
- package/dist/src/commands/pin.js +75 -0
- package/dist/src/commands/registry/add.js +62 -0
- package/dist/src/commands/registry/index.js +166 -0
- package/dist/src/commands/registry/install-bundles.js +36 -0
- package/dist/src/commands/registry/remove.js +18 -0
- package/dist/src/commands/registry/status.js +31 -0
- package/dist/src/commands/sensors/baseline.js +93 -0
- package/dist/src/commands/sensors/formatters/eslint.js +29 -0
- package/dist/src/commands/sensors/formatters/generic.js +8 -0
- package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
- package/dist/src/commands/sensors/formatters/test.js +12 -0
- package/dist/src/commands/sensors/formatters/tsc.js +23 -0
- package/dist/src/commands/sensors/index.js +94 -0
- package/dist/src/commands/sensors/init.js +112 -0
- package/dist/src/commands/sensors/install.js +78 -0
- package/dist/src/commands/sensors/run.js +217 -0
- package/dist/src/commands/sensors/status.js +85 -0
- package/dist/src/commands/sensors/types.js +2 -0
- package/dist/src/core/bundle-install.js +116 -0
- package/dist/src/core/bundles.js +122 -0
- package/dist/src/core/cli-version.js +30 -0
- package/dist/src/core/context/materializer.js +30 -0
- package/dist/src/core/context/orchestrator.js +75 -0
- package/dist/src/core/context/project-constitution-inject.js +47 -0
- package/dist/src/core/context/provider.js +29 -0
- package/dist/src/core/context/regenerate.js +61 -0
- package/dist/src/core/context/strategies/config-instructions.js +73 -0
- package/dist/src/core/context/strategies/hook-merge.js +23 -0
- package/dist/src/core/context/strategies/strategy.js +2 -0
- package/dist/src/core/context/types.js +2 -0
- package/dist/src/core/diagnostics/checks.js +141 -0
- package/dist/src/core/diagnostics/context.js +181 -0
- package/dist/src/core/diagnostics/types.js +2 -0
- package/dist/src/core/discovery.js +135 -0
- package/dist/src/core/executor.js +41 -0
- package/dist/src/core/init/detector.js +67 -0
- package/dist/src/core/init/orchestrator.js +54 -0
- package/dist/src/core/init/steps.js +279 -0
- package/dist/src/core/init/types.js +2 -0
- package/dist/src/core/ledger/store.js +73 -0
- package/dist/src/core/ledger/types.js +2 -0
- package/dist/src/core/miro.js +314 -0
- package/dist/src/core/profile-pins.js +36 -0
- package/dist/src/core/profile.js +146 -0
- package/dist/src/core/registries.js +205 -0
- package/dist/src/core/registry.js +28 -0
- package/dist/src/core/skill-integrity.js +97 -0
- package/dist/src/core/story-map-parser.js +83 -0
- package/dist/src/core/update-check-worker.js +10 -0
- package/dist/src/core/update-check.js +106 -0
- package/dist/src/core/versioning.js +112 -0
- package/dist/src/index.js +689 -0
- package/dist/src/providers/index.js +63 -0
- package/dist/src/utils/config.js +35 -0
- package/dist/src/utils/grouping.js +51 -0
- package/dist/src/utils/registry-view.js +154 -0
- package/dist/tests/commands/doctor.test.js +98 -0
- package/dist/tests/commands/hooks/install.test.js +149 -0
- package/dist/tests/commands/hooks/resync.test.js +135 -0
- package/dist/tests/commands/hooks/router.test.js +26 -0
- package/dist/tests/commands/hooks/status.test.js +88 -0
- package/dist/tests/commands/hooks/uninstall.test.js +104 -0
- package/dist/tests/commands/init.test.js +87 -0
- package/dist/tests/commands/ledger/index.test.js +64 -0
- package/dist/tests/commands/pin.test.js +72 -0
- package/dist/tests/commands/registry/add.test.js +223 -0
- package/dist/tests/commands/registry/install-bundles.test.js +87 -0
- package/dist/tests/commands/registry/remove.test.js +49 -0
- package/dist/tests/commands/registry/status.test.js +43 -0
- package/dist/tests/commands/sensors/baseline.test.js +106 -0
- package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
- package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
- package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
- package/dist/tests/commands/sensors/index.test.js +18 -0
- package/dist/tests/commands/sensors/init.test.js +137 -0
- package/dist/tests/commands/sensors/install.test.js +60 -0
- package/dist/tests/commands/sensors/router.test.js +23 -0
- package/dist/tests/commands/sensors/run.test.js +353 -0
- package/dist/tests/commands/sensors/status.test.js +98 -0
- package/dist/tests/core/base-remote.test.js +70 -0
- package/dist/tests/core/bundle-install.test.js +198 -0
- package/dist/tests/core/bundles-multiroot.test.js +68 -0
- package/dist/tests/core/bundles-overrides.test.js +49 -0
- package/dist/tests/core/bundles.test.js +96 -0
- package/dist/tests/core/cli-version.test.js +17 -0
- package/dist/tests/core/context/materializer.test.js +46 -0
- package/dist/tests/core/context/orchestrator.test.js +127 -0
- package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
- package/dist/tests/core/context/provider.test.js +45 -0
- package/dist/tests/core/context/regenerate.test.js +106 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
- package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
- package/dist/tests/core/context/types.test.js +15 -0
- package/dist/tests/core/diagnostics/checks.test.js +208 -0
- package/dist/tests/core/diagnostics/context.test.js +170 -0
- package/dist/tests/core/discovery-multiroot.test.js +63 -0
- package/dist/tests/core/discovery-overrides.test.js +105 -0
- package/dist/tests/core/discovery.test.js +113 -0
- package/dist/tests/core/executor.test.js +44 -0
- package/dist/tests/core/init/detector.test.js +56 -0
- package/dist/tests/core/init/orchestrator.test.js +122 -0
- package/dist/tests/core/init/steps.test.js +363 -0
- package/dist/tests/core/ledger/gitignore.test.js +13 -0
- package/dist/tests/core/ledger/store.test.js +122 -0
- package/dist/tests/core/miro-layout.test.js +150 -0
- package/dist/tests/core/profile-pins.test.js +131 -0
- package/dist/tests/core/profile-registries.test.js +59 -0
- package/dist/tests/core/profile.test.js +110 -0
- package/dist/tests/core/registries-capability.test.js +52 -0
- package/dist/tests/core/registries-seed.test.js +66 -0
- package/dist/tests/core/registries-sync.test.js +205 -0
- package/dist/tests/core/registries.test.js +91 -0
- package/dist/tests/core/registry-manifest.test.js +95 -0
- package/dist/tests/core/registry-versioned-sync.test.js +113 -0
- package/dist/tests/core/registry.test.js +51 -0
- package/dist/tests/core/skill-integrity.test.js +126 -0
- package/dist/tests/core/story-map-parser.test.js +136 -0
- package/dist/tests/core/sync-gates.test.js +97 -0
- package/dist/tests/core/update-check.test.js +106 -0
- package/dist/tests/core/versioning.test.js +169 -0
- package/dist/tests/integration/pack-e2e.test.js +50 -0
- package/dist/tests/providers/hooks-config.test.js +45 -0
- package/dist/tests/providers/index.test.js +58 -0
- package/dist/tests/providers/injection-config.test.js +25 -0
- package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
- package/dist/tests/registry/catalog-consistency.test.js +47 -0
- package/dist/tests/registry/design-skills.test.js +146 -0
- package/dist/tests/registry/prose-agnostic.test.js +18 -0
- package/dist/tests/registry/sensor-packs.test.js +45 -0
- package/dist/tests/registry/skill-versions.test.js +26 -0
- package/dist/tests/registry/using-awm.test.js +39 -0
- package/dist/tests/utils/config.test.js +31 -0
- package/dist/tests/utils/grouping.test.js +43 -0
- package/dist/tests/utils/registry-view-overrides.test.js +41 -0
- package/dist/tests/utils/registry-view.test.js +156 -0
- package/package.json +49 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// cli/tests/core/registries-seed.test.ts
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
describe('core/registries — seedBaselineRegistry + contentRoots sin base especial', () => {
|
|
11
|
+
let tmpHome;
|
|
12
|
+
let originalHome;
|
|
13
|
+
let originalAwmHome;
|
|
14
|
+
let originalBaseRemote;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-seed-'));
|
|
17
|
+
originalHome = process.env.HOME;
|
|
18
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
19
|
+
originalBaseRemote = process.env.AWM_BASE_REMOTE;
|
|
20
|
+
process.env.HOME = tmpHome;
|
|
21
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
22
|
+
delete process.env.AWM_BASE_REMOTE;
|
|
23
|
+
jest.resetModules();
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
27
|
+
if (originalHome === undefined)
|
|
28
|
+
delete process.env.HOME;
|
|
29
|
+
else
|
|
30
|
+
process.env.HOME = originalHome;
|
|
31
|
+
if (originalAwmHome === undefined)
|
|
32
|
+
delete process.env.AWM_HOME;
|
|
33
|
+
else
|
|
34
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
35
|
+
if (originalBaseRemote === undefined)
|
|
36
|
+
delete process.env.AWM_BASE_REMOTE;
|
|
37
|
+
else
|
|
38
|
+
process.env.AWM_BASE_REMOTE = originalBaseRemote;
|
|
39
|
+
});
|
|
40
|
+
it('seedBaselineRegistry crea registries.json con baseline la primera vez', () => {
|
|
41
|
+
const m = require('../../src/core/registries');
|
|
42
|
+
expect(m.seedBaselineRegistry()).toBe(true);
|
|
43
|
+
expect(m.readRegistriesConfig()).toEqual([
|
|
44
|
+
{ name: 'baseline', remote: require('../../src/core/registry').DEFAULT_REMOTE },
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
it('seedBaselineRegistry respeta AWM_BASE_REMOTE', () => {
|
|
48
|
+
process.env.AWM_BASE_REMOTE = '/tmp/mi-remote';
|
|
49
|
+
jest.resetModules();
|
|
50
|
+
const m = require('../../src/core/registries');
|
|
51
|
+
m.seedBaselineRegistry();
|
|
52
|
+
expect(m.readRegistriesConfig()[0].remote).toBe('/tmp/mi-remote');
|
|
53
|
+
delete process.env.AWM_BASE_REMOTE;
|
|
54
|
+
});
|
|
55
|
+
it('seedBaselineRegistry es no-op si registries.json ya existe (idempotente, respeta ediciones)', () => {
|
|
56
|
+
const m = require('../../src/core/registries');
|
|
57
|
+
m.writeRegistriesConfig([{ name: 'equipo', remote: 'x' }]);
|
|
58
|
+
expect(m.seedBaselineRegistry()).toBe(false);
|
|
59
|
+
expect(m.readRegistriesConfig()).toEqual([{ name: 'equipo', remote: 'x' }]);
|
|
60
|
+
});
|
|
61
|
+
it('contentRoots ya no incluye un root base especial', () => {
|
|
62
|
+
const m = require('../../src/core/registries');
|
|
63
|
+
m.writeRegistriesConfig([]);
|
|
64
|
+
expect(m.contentRoots()).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// cli/tests/core/registries-sync.test.ts
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const GIT = (cwd, cmd) => (0, child_process_1.execSync)(`git -c user.email=t@t.t -c user.name=t -c tag.gpgSign=false ${cmd}`, { cwd, stdio: 'pipe' });
|
|
12
|
+
/** Creates a git source repo with a skill, returns its path (serves as local remote). */
|
|
13
|
+
function makeSourceRepo(base, skillName) {
|
|
14
|
+
const dir = path_1.default.join(base, `src-${skillName}`);
|
|
15
|
+
fs_1.default.mkdirSync(path_1.default.join(dir, 'skills', skillName), { recursive: true });
|
|
16
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'skills', skillName, 'SKILL.md'), `---\nname: ${skillName}\ndescription: test skill\n---\n# ${skillName}\n`);
|
|
17
|
+
GIT(dir, 'init -q');
|
|
18
|
+
GIT(dir, 'add -A');
|
|
19
|
+
GIT(dir, 'commit -qm init');
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
describe('syncRegistries (git fixtures locales)', () => {
|
|
23
|
+
let tmpHome;
|
|
24
|
+
let tmpWork;
|
|
25
|
+
let originalHome;
|
|
26
|
+
let originalAwmHome;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regsync-home-'));
|
|
29
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regsync-work-'));
|
|
30
|
+
originalHome = process.env.HOME;
|
|
31
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
32
|
+
process.env.HOME = tmpHome;
|
|
33
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
34
|
+
jest.resetModules();
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
38
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
39
|
+
if (originalHome === undefined)
|
|
40
|
+
delete process.env.HOME;
|
|
41
|
+
else
|
|
42
|
+
process.env.HOME = originalHome;
|
|
43
|
+
if (originalAwmHome === undefined)
|
|
44
|
+
delete process.env.AWM_HOME;
|
|
45
|
+
else
|
|
46
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
47
|
+
});
|
|
48
|
+
it('re-clones a configured registry missing on disk', async () => {
|
|
49
|
+
const m = require('../../src/core/registries');
|
|
50
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
51
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
52
|
+
const results = await m.syncRegistries();
|
|
53
|
+
expect(results).toEqual([{ name: 'personal', action: 'recloned', version: 'HEAD' }]);
|
|
54
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.awm/registries/personal/skills/alpha/SKILL.md'))).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it('pulls an existing clone and reports non-fatal errors per registry', async () => {
|
|
57
|
+
const m = require('../../src/core/registries');
|
|
58
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
59
|
+
m.writeRegistriesConfig([
|
|
60
|
+
{ name: 'personal', remote: source },
|
|
61
|
+
{ name: 'broken', remote: path_1.default.join(tmpWork, 'does-not-exist') },
|
|
62
|
+
]);
|
|
63
|
+
await m.syncRegistries(); // first pass: clones 'personal', fails 'broken'
|
|
64
|
+
// advance the remote
|
|
65
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\ndescription: v2\n---\n');
|
|
66
|
+
GIT(source, 'add -A');
|
|
67
|
+
GIT(source, 'commit -qm v2');
|
|
68
|
+
const results = await m.syncRegistries();
|
|
69
|
+
expect(results[0]).toEqual({ name: 'personal', action: 'pulled', version: 'HEAD' });
|
|
70
|
+
expect(results[1].name).toBe('broken');
|
|
71
|
+
expect(results[1].action).toBe('error');
|
|
72
|
+
const synced = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/registries/personal/skills/alpha/SKILL.md'), 'utf-8');
|
|
73
|
+
expect(synced).toContain('v2');
|
|
74
|
+
});
|
|
75
|
+
it('registry con tags queda en el último tag y reporta la versión', async () => {
|
|
76
|
+
const m = require('../../src/core/registries');
|
|
77
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
78
|
+
GIT(source, 'tag v1.0.0');
|
|
79
|
+
// commit post-tag: HEAD va más allá del release
|
|
80
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\ndescription: unreleased\n---\n');
|
|
81
|
+
GIT(source, 'add -A');
|
|
82
|
+
GIT(source, 'commit -qm unreleased');
|
|
83
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
84
|
+
const results = await m.syncRegistries();
|
|
85
|
+
expect(results).toEqual([{ name: 'personal', action: 'recloned', version: 'v1.0.0' }]);
|
|
86
|
+
const synced = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/registries/personal/skills/alpha/SKILL.md'), 'utf-8');
|
|
87
|
+
expect(synced).toContain('test skill'); // contenido del tag, no del HEAD
|
|
88
|
+
});
|
|
89
|
+
it('F2 regression: clone fresco limpiado si pin inexistente falla post-clone', async () => {
|
|
90
|
+
const m = require('../../src/core/registries');
|
|
91
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
92
|
+
GIT(source, 'tag v1.0.0');
|
|
93
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
94
|
+
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
95
|
+
fs_1.default.mkdirSync(awmDir, { recursive: true });
|
|
96
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude', installMethod: 'symlink', defaultScope: 'local', pins: { personal: '9.9.9' } }));
|
|
97
|
+
const results = await m.syncRegistries();
|
|
98
|
+
expect(results[0].action).toBe('error');
|
|
99
|
+
// El dir no debe quedar en disco tras el fallo
|
|
100
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.awm/registries/personal'))).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
it('pin por nombre en preferences gana sobre el último tag', async () => {
|
|
103
|
+
const m = require('../../src/core/registries');
|
|
104
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
105
|
+
GIT(source, 'tag v1.0.0');
|
|
106
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\ndescription: v2\n---\n');
|
|
107
|
+
GIT(source, 'add -A');
|
|
108
|
+
GIT(source, 'commit -qm v2');
|
|
109
|
+
GIT(source, 'tag v1.1.0');
|
|
110
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
111
|
+
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
112
|
+
fs_1.default.mkdirSync(awmDir, { recursive: true });
|
|
113
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude', installMethod: 'symlink', defaultScope: 'local', pins: { personal: '1.0.0' } }));
|
|
114
|
+
const results = await m.syncRegistries();
|
|
115
|
+
expect(results).toEqual([{ name: 'personal', action: 'recloned', version: 'v1.0.0' }]);
|
|
116
|
+
});
|
|
117
|
+
it('baseline sembrado se sincroniza por el mismo loop que cualquier registry', async () => {
|
|
118
|
+
const m = require('../../src/core/registries');
|
|
119
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
120
|
+
GIT(source, 'tag v1.0.0');
|
|
121
|
+
process.env.AWM_BASE_REMOTE = source;
|
|
122
|
+
m.seedBaselineRegistry();
|
|
123
|
+
delete process.env.AWM_BASE_REMOTE;
|
|
124
|
+
const results = await m.syncRegistries();
|
|
125
|
+
expect(results).toEqual([{ name: 'baseline', action: 'recloned', version: 'v1.0.0' }]);
|
|
126
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.awm/registries/baseline/skills/alpha/SKILL.md'))).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
it('verifyMinCliVersions reporta registries que exigen CLI más nuevo', async () => {
|
|
129
|
+
const m = require('../../src/core/registries');
|
|
130
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
131
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'awm-registry.json'), JSON.stringify({ minCliVersion: '99.0.0' }));
|
|
132
|
+
GIT(source, 'add -A');
|
|
133
|
+
GIT(source, 'commit -qm manifest');
|
|
134
|
+
m.writeRegistriesConfig([{ name: 'exigente', remote: source }]);
|
|
135
|
+
await m.syncRegistries();
|
|
136
|
+
const failures = m.verifyMinCliVersions();
|
|
137
|
+
expect(failures).toEqual([{ name: 'exigente', min: '99.0.0' }]);
|
|
138
|
+
});
|
|
139
|
+
it('verifyMinCliVersions ignora registries sin campo o ausentes en disco', () => {
|
|
140
|
+
const m = require('../../src/core/registries');
|
|
141
|
+
m.writeRegistriesConfig([{ name: 'fantasma', remote: '/no/existe' }]);
|
|
142
|
+
expect(m.verifyMinCliVersions()).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
it('transición tag→tag: clone existente en v1.0.0 avanza a v1.1.0 tras nuevo release', async () => {
|
|
145
|
+
const m = require('../../src/core/registries');
|
|
146
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
147
|
+
GIT(source, 'tag v1.0.0');
|
|
148
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
149
|
+
// primera sync: queda en v1.0.0
|
|
150
|
+
const r1 = await m.syncRegistries();
|
|
151
|
+
expect(r1).toEqual([{ name: 'personal', action: 'recloned', version: 'v1.0.0' }]);
|
|
152
|
+
// nueva release en el remote
|
|
153
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\ndescription: v2\n---\n');
|
|
154
|
+
GIT(source, 'add -A');
|
|
155
|
+
GIT(source, 'commit -qm v1.1.0');
|
|
156
|
+
GIT(source, 'tag v1.1.0');
|
|
157
|
+
// segunda sync: avanza al nuevo tag
|
|
158
|
+
const r2 = await m.syncRegistries();
|
|
159
|
+
expect(r2).toEqual([{ name: 'personal', action: 'pulled', version: 'v1.1.0' }]);
|
|
160
|
+
const content = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/registries/personal/skills/alpha/SKILL.md'), 'utf-8');
|
|
161
|
+
expect(content).toContain('v2');
|
|
162
|
+
});
|
|
163
|
+
it('rollback tag→tag: clone en v1.1.0 retrocede a v1.0.0 al establecer pin', async () => {
|
|
164
|
+
const m = require('../../src/core/registries');
|
|
165
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
166
|
+
GIT(source, 'tag v1.0.0');
|
|
167
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\ndescription: v2\n---\n');
|
|
168
|
+
GIT(source, 'add -A');
|
|
169
|
+
GIT(source, 'commit -qm v1.1.0');
|
|
170
|
+
GIT(source, 'tag v1.1.0');
|
|
171
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
172
|
+
// primera sync sin pin: queda en v1.1.0
|
|
173
|
+
const r1 = await m.syncRegistries();
|
|
174
|
+
expect(r1).toEqual([{ name: 'personal', action: 'recloned', version: 'v1.1.0' }]);
|
|
175
|
+
// establece pin a la versión anterior
|
|
176
|
+
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
177
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude', installMethod: 'symlink', defaultScope: 'local', pins: { personal: '1.0.0' } }));
|
|
178
|
+
// segunda sync con pin: retrocede a v1.0.0
|
|
179
|
+
const r2 = await m.syncRegistries();
|
|
180
|
+
expect(r2).toEqual([{ name: 'personal', action: 'pulled', version: 'v1.0.0' }]);
|
|
181
|
+
const content = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/registries/personal/skills/alpha/SKILL.md'), 'utf-8');
|
|
182
|
+
expect(content).toContain('test skill'); // contenido original del tag v1.0.0
|
|
183
|
+
});
|
|
184
|
+
it('canal dev: preferences.channel=dev sigue HEAD y recibe commits nuevos', async () => {
|
|
185
|
+
const m = require('../../src/core/registries');
|
|
186
|
+
const source = makeSourceRepo(tmpWork, 'alpha');
|
|
187
|
+
GIT(source, 'tag v1.0.0');
|
|
188
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: source }]);
|
|
189
|
+
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
190
|
+
fs_1.default.mkdirSync(awmDir, { recursive: true });
|
|
191
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude', installMethod: 'symlink', defaultScope: 'local', channel: 'dev' }));
|
|
192
|
+
// primera sync en canal dev: queda en HEAD (no en el tag)
|
|
193
|
+
const r1 = await m.syncRegistries();
|
|
194
|
+
expect(r1).toEqual([{ name: 'personal', action: 'recloned', version: 'HEAD' }]);
|
|
195
|
+
// nuevo commit en el remote (post-tag, no release)
|
|
196
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\ndescription: head-2\n---\n');
|
|
197
|
+
GIT(source, 'add -A');
|
|
198
|
+
GIT(source, 'commit -qm head-2');
|
|
199
|
+
// segunda sync: recibe el nuevo commit
|
|
200
|
+
const r2 = await m.syncRegistries();
|
|
201
|
+
expect(r2).toEqual([{ name: 'personal', action: 'pulled', version: 'HEAD' }]);
|
|
202
|
+
const content = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/registries/personal/skills/alpha/SKILL.md'), 'utf-8');
|
|
203
|
+
expect(content).toContain('head-2');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// cli/tests/core/registries.test.ts
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
describe('core/registries', () => {
|
|
11
|
+
let tmpHome;
|
|
12
|
+
let originalHome;
|
|
13
|
+
let originalAwmHome;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-registries-'));
|
|
16
|
+
originalHome = process.env.HOME;
|
|
17
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
18
|
+
process.env.HOME = tmpHome;
|
|
19
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
20
|
+
jest.resetModules();
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
24
|
+
if (originalHome === undefined)
|
|
25
|
+
delete process.env.HOME;
|
|
26
|
+
else
|
|
27
|
+
process.env.HOME = originalHome;
|
|
28
|
+
if (originalAwmHome === undefined)
|
|
29
|
+
delete process.env.AWM_HOME;
|
|
30
|
+
else
|
|
31
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
32
|
+
});
|
|
33
|
+
function load() {
|
|
34
|
+
return require('../../src/core/registries');
|
|
35
|
+
}
|
|
36
|
+
it('readRegistriesConfig returns [] when the config file does not exist', () => {
|
|
37
|
+
expect(load().readRegistriesConfig()).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
it('write + read round-trips entries', () => {
|
|
40
|
+
const m = load();
|
|
41
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: 'git@github.com:x/y.git' }]);
|
|
42
|
+
expect(m.readRegistriesConfig()).toEqual([{ name: 'personal', remote: 'git@github.com:x/y.git' }]);
|
|
43
|
+
});
|
|
44
|
+
it('readRegistriesConfig throws an explicit error naming the path on corrupt JSON', () => {
|
|
45
|
+
const m = load();
|
|
46
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm'), { recursive: true });
|
|
47
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm', 'registries.json'), '{not json');
|
|
48
|
+
expect(() => m.readRegistriesConfig()).toThrow(/registries\.json/);
|
|
49
|
+
});
|
|
50
|
+
it('readRegistriesConfig throws on non-array or malformed entries', () => {
|
|
51
|
+
const m = load();
|
|
52
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm'), { recursive: true });
|
|
53
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm', 'registries.json'), JSON.stringify({ foo: 1 }));
|
|
54
|
+
expect(() => m.readRegistriesConfig()).toThrow(/expected a JSON array/);
|
|
55
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm', 'registries.json'), JSON.stringify([{ name: 'x' }]));
|
|
56
|
+
expect(() => m.readRegistriesConfig()).toThrow(/malformed entry/);
|
|
57
|
+
});
|
|
58
|
+
it('readRegistriesConfig rejects single-dot name to prevent path traversal', () => {
|
|
59
|
+
const m = load();
|
|
60
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm'), { recursive: true });
|
|
61
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm', 'registries.json'), JSON.stringify([{ name: '.', remote: 'r' }]));
|
|
62
|
+
expect(() => m.readRegistriesConfig()).toThrow(/path traversal/);
|
|
63
|
+
});
|
|
64
|
+
it('listRegistries derives contentRoot under ~/.awm/registries/<name>', () => {
|
|
65
|
+
const m = load();
|
|
66
|
+
m.writeRegistriesConfig([{ name: 'personal', remote: 'r' }]);
|
|
67
|
+
expect(m.listRegistries()).toEqual([
|
|
68
|
+
{ name: 'personal', remote: 'r', contentRoot: path_1.default.join(tmpHome, '.awm', 'registries', 'personal') },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
it('contentRoots includes only configured registries present on disk', () => {
|
|
72
|
+
const m = load();
|
|
73
|
+
// 'present' existe en disco, 'ghost' no
|
|
74
|
+
const present = path_1.default.join(tmpHome, '.awm', 'registries', 'present');
|
|
75
|
+
fs_1.default.mkdirSync(present, { recursive: true });
|
|
76
|
+
m.writeRegistriesConfig([{ name: 'present', remote: 'r1' }, { name: 'ghost', remote: 'r2' }]);
|
|
77
|
+
expect(m.contentRoots()).toEqual([present]);
|
|
78
|
+
});
|
|
79
|
+
it('contentRoots returns [] when no registries are configured', () => {
|
|
80
|
+
const m = load();
|
|
81
|
+
expect(m.contentRoots()).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
it('validateRegistryLayout requires at least one content dir at the root', () => {
|
|
84
|
+
const m = load();
|
|
85
|
+
const root = path_1.default.join(tmpHome, 'somerepo');
|
|
86
|
+
fs_1.default.mkdirSync(root, { recursive: true });
|
|
87
|
+
expect(m.validateRegistryLayout(root)).toBe(false);
|
|
88
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills'));
|
|
89
|
+
expect(m.validateRegistryLayout(root)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// cli/tests/core/registry-manifest.test.ts
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
describe('registry manifest (awm-registry.json)', () => {
|
|
11
|
+
let tmpHome;
|
|
12
|
+
let tmpWork;
|
|
13
|
+
const origHome = process.env.HOME;
|
|
14
|
+
const origAwmHome = process.env.AWM_HOME;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-home-'));
|
|
17
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-work-'));
|
|
18
|
+
process.env.HOME = tmpHome;
|
|
19
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
20
|
+
jest.resetModules();
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.env.HOME = origHome;
|
|
24
|
+
if (origAwmHome === undefined)
|
|
25
|
+
delete process.env.AWM_HOME;
|
|
26
|
+
else
|
|
27
|
+
process.env.AWM_HOME = origAwmHome;
|
|
28
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
29
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
function load() {
|
|
32
|
+
return require('../../src/core/registries');
|
|
33
|
+
}
|
|
34
|
+
it('returns empty overrides when manifest file is absent', () => {
|
|
35
|
+
const { readRegistryManifest } = load();
|
|
36
|
+
const m = readRegistryManifest(tmpWork);
|
|
37
|
+
expect(m.overrides.size).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
it('parses a valid manifest into a Set of names', () => {
|
|
40
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), JSON.stringify({ overrides: ['brainstorming', 'writing-plans'] }));
|
|
41
|
+
const { readRegistryManifest } = load();
|
|
42
|
+
const m = readRegistryManifest(tmpWork);
|
|
43
|
+
expect(m.overrides.has('brainstorming')).toBe(true);
|
|
44
|
+
expect(m.overrides.has('writing-plans')).toBe(true);
|
|
45
|
+
expect(m.overrides.size).toBe(2);
|
|
46
|
+
});
|
|
47
|
+
it('treats a manifest without "overrides" key as empty', () => {
|
|
48
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), JSON.stringify({}));
|
|
49
|
+
const { readRegistryManifest } = load();
|
|
50
|
+
expect(readRegistryManifest(tmpWork).overrides.size).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
it('throws with the file path on corrupt JSON — never silently empty', () => {
|
|
53
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), '{nope');
|
|
54
|
+
const { readRegistryManifest } = load();
|
|
55
|
+
expect(() => readRegistryManifest(tmpWork)).toThrow(/awm-registry\.json/);
|
|
56
|
+
});
|
|
57
|
+
it('throws when overrides is not an array of strings', () => {
|
|
58
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), JSON.stringify({ overrides: 'brainstorming' }));
|
|
59
|
+
const { readRegistryManifest } = load();
|
|
60
|
+
expect(() => readRegistryManifest(tmpWork)).toThrow(/array of strings/);
|
|
61
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), JSON.stringify({ overrides: [42] }));
|
|
62
|
+
jest.resetModules();
|
|
63
|
+
expect(() => load().readRegistryManifest(tmpWork)).toThrow(/array of strings/);
|
|
64
|
+
});
|
|
65
|
+
it.each(['', '.', '..', 'a/b', 'a\\b', '../up'])('rejects override name %j (path traversal guard)', (bad) => {
|
|
66
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), JSON.stringify({ overrides: [bad] }));
|
|
67
|
+
const { readRegistryManifest } = load();
|
|
68
|
+
expect(() => readRegistryManifest(tmpWork)).toThrow(/path traversal/);
|
|
69
|
+
});
|
|
70
|
+
it('registryNameForPath maps configured registries and returns null for unknown paths', () => {
|
|
71
|
+
const m = load();
|
|
72
|
+
const regPath = path_1.default.join(m.REGISTRIES_DIR, 'team-acme', 'skills', 'x');
|
|
73
|
+
expect(m.registryNameForPath(regPath)).toBe('team-acme');
|
|
74
|
+
expect(m.registryNameForPath('/somewhere/else')).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
// minCliVersion tests (WS-4)
|
|
77
|
+
function writeManifest(data) {
|
|
78
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'awm-registry.json'), JSON.stringify(data));
|
|
79
|
+
}
|
|
80
|
+
it('minCliVersion válido se expone normalizado (acepta prefijo v)', () => {
|
|
81
|
+
writeManifest({ minCliVersion: 'v2.1.0' });
|
|
82
|
+
const { readRegistryManifest } = load();
|
|
83
|
+
expect(readRegistryManifest(tmpWork).minCliVersion).toBe('2.1.0');
|
|
84
|
+
});
|
|
85
|
+
it('minCliVersion ausente → undefined', () => {
|
|
86
|
+
writeManifest({ overrides: [] });
|
|
87
|
+
const { readRegistryManifest } = load();
|
|
88
|
+
expect(readRegistryManifest(tmpWork).minCliVersion).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
it.each([['banana'], ['2.1'], [2], [null]])('minCliVersion malformado %p → error explícito', (bad) => {
|
|
91
|
+
writeManifest({ minCliVersion: bad });
|
|
92
|
+
const { readRegistryManifest } = load();
|
|
93
|
+
expect(() => readRegistryManifest(tmpWork)).toThrow(/minCliVersion/);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// cli/tests/core/registry-versioned-sync.test.ts
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const GIT = (cwd, cmd) => (0, child_process_1.execSync)(`git -c user.email=t@t.t -c user.name=t -c tag.gpgSign=false ${cmd}`, { cwd, stdio: 'pipe' });
|
|
12
|
+
function makeTaggedRepo(base, name, versions) {
|
|
13
|
+
const dir = path_1.default.join(base, name);
|
|
14
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
15
|
+
GIT(dir, 'init -q -b main');
|
|
16
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'VERSION'), 'init');
|
|
17
|
+
GIT(dir, 'add -A');
|
|
18
|
+
GIT(dir, 'commit -qm init');
|
|
19
|
+
for (const v of versions) {
|
|
20
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'VERSION'), v);
|
|
21
|
+
GIT(dir, 'add -A');
|
|
22
|
+
GIT(dir, `commit -qm ${v}`);
|
|
23
|
+
GIT(dir, `tag v${v}`);
|
|
24
|
+
}
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
function addRelease(source, version) {
|
|
28
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), version);
|
|
29
|
+
GIT(source, 'add -A');
|
|
30
|
+
GIT(source, `commit -qm ${version}`);
|
|
31
|
+
GIT(source, `tag v${version}`);
|
|
32
|
+
}
|
|
33
|
+
describe('syncRegistry versionado (fixtures git locales)', () => {
|
|
34
|
+
let tmpHome;
|
|
35
|
+
let tmpWork;
|
|
36
|
+
let originalHome;
|
|
37
|
+
let originalAwmHome;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regver-home-'));
|
|
40
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regver-work-'));
|
|
41
|
+
originalHome = process.env.HOME;
|
|
42
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
43
|
+
process.env.HOME = tmpHome;
|
|
44
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
45
|
+
jest.resetModules();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
49
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
50
|
+
if (originalHome === undefined)
|
|
51
|
+
delete process.env.HOME;
|
|
52
|
+
else
|
|
53
|
+
process.env.HOME = originalHome;
|
|
54
|
+
if (originalAwmHome === undefined)
|
|
55
|
+
delete process.env.AWM_HOME;
|
|
56
|
+
else
|
|
57
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
58
|
+
});
|
|
59
|
+
const registryVersionFile = () => path_1.default.join(process.env.AWM_HOME, 'cli-source/VERSION');
|
|
60
|
+
it('clone fresco queda checkouteado en el último tag (no en HEAD)', async () => {
|
|
61
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
62
|
+
// commit post-tag: HEAD del remote va más allá del último release
|
|
63
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), 'unreleased');
|
|
64
|
+
GIT(source, 'add -A');
|
|
65
|
+
GIT(source, 'commit -qm unreleased');
|
|
66
|
+
const { syncRegistry } = require('../../src/core/registry');
|
|
67
|
+
const resolved = await syncRegistry(source, { channel: 'stable' });
|
|
68
|
+
expect(resolved).toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
69
|
+
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.0.0');
|
|
70
|
+
});
|
|
71
|
+
it('clone existente transiciona al tag nuevo tras un release en el remote', async () => {
|
|
72
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
73
|
+
const { syncRegistry } = require('../../src/core/registry');
|
|
74
|
+
await syncRegistry(source, { channel: 'stable' });
|
|
75
|
+
addRelease(source, '1.1.0');
|
|
76
|
+
const resolved = await syncRegistry(source, { channel: 'stable' });
|
|
77
|
+
expect(resolved).toEqual({ kind: 'tag', ref: 'v1.1.0', version: '1.1.0' });
|
|
78
|
+
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.1.0');
|
|
79
|
+
});
|
|
80
|
+
it('rollback: pin a un tag anterior vuelve el contenido a esa versión', async () => {
|
|
81
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0', '1.1.0']);
|
|
82
|
+
const { syncRegistry } = require('../../src/core/registry');
|
|
83
|
+
await syncRegistry(source, { channel: 'stable' });
|
|
84
|
+
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.1.0');
|
|
85
|
+
const resolved = await syncRegistry(source, { pin: '1.0.0', channel: 'stable' });
|
|
86
|
+
expect(resolved).toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
87
|
+
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.0.0');
|
|
88
|
+
});
|
|
89
|
+
it('canal dev sigue HEAD del branch y recibe commits nuevos', async () => {
|
|
90
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
91
|
+
const { syncRegistry } = require('../../src/core/registry');
|
|
92
|
+
const first = await syncRegistry(source, { channel: 'dev' });
|
|
93
|
+
expect(first).toEqual({ kind: 'head', ref: 'main' });
|
|
94
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), 'head-2');
|
|
95
|
+
GIT(source, 'add -A');
|
|
96
|
+
GIT(source, 'commit -qm head-2');
|
|
97
|
+
await syncRegistry(source, { channel: 'dev' });
|
|
98
|
+
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('head-2');
|
|
99
|
+
});
|
|
100
|
+
it('repo sin tags en canal stable → head-fallback y sigue HEAD', async () => {
|
|
101
|
+
const source = makeTaggedRepo(tmpWork, 'src', []);
|
|
102
|
+
const { syncRegistry } = require('../../src/core/registry');
|
|
103
|
+
const resolved = await syncRegistry(source, { channel: 'stable' });
|
|
104
|
+
expect(resolved).toEqual({ kind: 'head-fallback', ref: 'main' });
|
|
105
|
+
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('init');
|
|
106
|
+
});
|
|
107
|
+
it('sin opts (callers legacy) → comportamiento stable por default', async () => {
|
|
108
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['2.0.0']);
|
|
109
|
+
const { syncRegistry } = require('../../src/core/registry');
|
|
110
|
+
const resolved = await syncRegistry(source);
|
|
111
|
+
expect(resolved).toEqual({ kind: 'tag', ref: 'v2.0.0', version: '2.0.0' });
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const child_process_1 = require("child_process");
|
|
4
|
+
jest.mock('child_process');
|
|
5
|
+
const mockSpawnSync = child_process_1.spawnSync;
|
|
6
|
+
describe('buildCli', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
it('returns success when npm run build exits 0', () => {
|
|
11
|
+
mockSpawnSync.mockReturnValue({ status: 0, stderr: Buffer.from(''), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
12
|
+
const { buildCli } = require('../../src/core/registry');
|
|
13
|
+
const result = buildCli('/fake/cli');
|
|
14
|
+
expect(result).toEqual({ success: true });
|
|
15
|
+
expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['run', 'build'], expect.objectContaining({ cwd: '/fake/cli', shell: true }));
|
|
16
|
+
});
|
|
17
|
+
it('returns failure with error message when build exits non-zero', () => {
|
|
18
|
+
mockSpawnSync.mockReturnValue({ status: 1, stderr: Buffer.from('tsc error: Type mismatch'), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
19
|
+
const { buildCli } = require('../../src/core/registry');
|
|
20
|
+
const result = buildCli('/fake/cli');
|
|
21
|
+
expect(result.success).toBe(false);
|
|
22
|
+
expect(result.error).toContain('tsc error');
|
|
23
|
+
});
|
|
24
|
+
it('returns failure with stdout message when tsc writes errors to stdout', () => {
|
|
25
|
+
mockSpawnSync.mockReturnValue({ status: 2, stderr: Buffer.from(''), stdout: Buffer.from('error TS2322: Type mismatch'), pid: 1, output: [], signal: null });
|
|
26
|
+
const { buildCli } = require('../../src/core/registry');
|
|
27
|
+
const result = buildCli('/fake/cli');
|
|
28
|
+
expect(result.success).toBe(false);
|
|
29
|
+
expect(result.error).toContain('TS2322');
|
|
30
|
+
});
|
|
31
|
+
it('returns failure when spawnSync throws unexpectedly', () => {
|
|
32
|
+
mockSpawnSync.mockImplementation(() => { throw new Error('unexpected error'); });
|
|
33
|
+
const { buildCli } = require('../../src/core/registry');
|
|
34
|
+
const result = buildCli('/fake/cli');
|
|
35
|
+
expect(result.success).toBe(false);
|
|
36
|
+
expect(result.error).toBe('unexpected error');
|
|
37
|
+
});
|
|
38
|
+
it('returns failure when npm is not found (shell returns status 127)', () => {
|
|
39
|
+
mockSpawnSync.mockReturnValue({ status: 127, stderr: Buffer.from('/bin/sh: npm: not found'), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
40
|
+
const { buildCli } = require('../../src/core/registry');
|
|
41
|
+
const result = buildCli('/fake/cli');
|
|
42
|
+
expect(result.success).toBe(false);
|
|
43
|
+
expect(result.error).toContain('not found');
|
|
44
|
+
});
|
|
45
|
+
it('uses REGISTRY_DIR/cli as default cwd', () => {
|
|
46
|
+
mockSpawnSync.mockReturnValue({ status: 0, stderr: Buffer.from(''), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
47
|
+
const { buildCli, REGISTRY_DIR } = require('../../src/core/registry');
|
|
48
|
+
buildCli();
|
|
49
|
+
expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['run', 'build'], expect.objectContaining({ cwd: `${REGISTRY_DIR}/cli` }));
|
|
50
|
+
});
|
|
51
|
+
});
|