@hivemagnus/hive-skills 1.1.0 → 1.2.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/README.md +14 -3
- package/dist/app.js +9 -2
- package/dist/cli.js +36 -3
- package/dist/db.js +2 -52
- package/dist/deployment-worker.js +7 -3
- package/dist/env-schema.js +2 -1
- package/dist/migrations/managed-format-1.js +54 -0
- package/dist/release/ZOD_LICENSE +21 -0
- package/dist/release/runtime.js +23781 -0
- package/dist/schema.js +2 -0
- package/dist/seed.js +3 -1
- package/dist/server.js +15 -4
- package/dist/ui.js +15 -0
- package/dist/upgrade.js +61 -0
- package/package.json +180 -4
package/README.md
CHANGED
|
@@ -19,17 +19,18 @@ OS-backed sign-in. SQLite is provided by Node; there are no native npm add-ons.
|
|
|
19
19
|
Agent deployment also needs Bash, OpenSSH, and permission to enable the target's user timer.
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install --prefix /opt/hive-skills/runtime @hivemagnus/hive-skills@1.
|
|
22
|
+
npm install --prefix /opt/hive-skills/runtime @hivemagnus/hive-skills@1.2.0
|
|
23
23
|
/opt/hive-skills/runtime/node_modules/.bin/hive-skills --env-file /opt/hive-skills/skills.env
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Or run `npx @hivemagnus/hive-skills --env-file skills.env`. The CLI accepts `start`
|
|
27
|
-
(the default), `seed`, `check`, `env-schema`, `--help` and `--version`. `check` validates TLS,
|
|
27
|
+
(the default), `seed`, `check`, `env-schema`, `migrate`, `upgrade`, `rollback`, `--help` and `--version`. `check` validates TLS,
|
|
28
28
|
configuration and an existing database without creating or migrating state. `env-schema` prints
|
|
29
29
|
configuration metadata without loading runtime configuration. Environment-file paths may be absolute;
|
|
30
30
|
existing environment variables win. Without `--env-file`, the historical `./.env` lookup
|
|
31
31
|
is retained. Help/version do not initialize storage. A fresh install starts with an empty
|
|
32
|
-
catalog; startup never seeds or replaces existing records.
|
|
32
|
+
catalog; startup never seeds or replaces existing records. Run `hive-skills migrate`
|
|
33
|
+
explicitly before first startup, or enable `HIVE_AUTO_MIGRATE=true` with backup access.
|
|
33
34
|
|
|
34
35
|
Configure these values in a private environment file (mode 0600):
|
|
35
36
|
|
|
@@ -155,3 +156,13 @@ version in `package.json`. CI also checks the package allowlist and npm 12 insta
|
|
|
155
156
|
The package publishes from `.github/workflows/publish.yml` alongside the other packages;
|
|
156
157
|
configure its npm trusted publisher for that workflow and the `npm-publish` environment after
|
|
157
158
|
the initial authenticated publication.
|
|
159
|
+
|
|
160
|
+
## Managed upgrades
|
|
161
|
+
|
|
162
|
+
Use `hive upgrade --dry-run`, then `hive upgrade` for the enrolled release bundle. This server's
|
|
163
|
+
`upgrade` and `rollback` commands delegate to the same owner-run launcher. `migrate --plan --json`
|
|
164
|
+
is read-only; migration requires a verified backup. Set `HIVE_AUTO_MIGRATE=true` explicitly for
|
|
165
|
+
startup migrations outside a managed install. Slow or one-way automatic migrations are refused.
|
|
166
|
+
`check --json` reports configuration without initializing state. No upgrade rotates signing keys
|
|
167
|
+
or reseeds existing accounts. See [UPGRADING.md](https://github.com/ChefTaco/hive/blob/main/UPGRADING.md)
|
|
168
|
+
for adoption, compatibility, recovery, update notices and container controls.
|
package/dist/app.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { maintenanceActive } from './release/runtime.js';
|
|
2
|
+
import { metadata } from './release/runtime.js';
|
|
3
|
+
import { packageRoot as releasePackageRoot } from './paths.js';
|
|
4
|
+
import { home as upgradeHome, updateStatus } from './release/runtime.js';
|
|
1
5
|
import { bodyLimit } from 'hono/body-limit';
|
|
2
6
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
7
|
import { Hono } from 'hono';
|
|
@@ -50,6 +54,8 @@ const archiveUrl = (skillSlug, release, channel) => {
|
|
|
50
54
|
const base = `${config.publicUrl}/v1/skills/${skillSlug}/releases/${release.version}/archive`;
|
|
51
55
|
return channel === 'stable' ? base : `${base}?channel=${channel}`;
|
|
52
56
|
};
|
|
57
|
+
app.use('*', async (c, next) => { if (maintenanceActive(new URL(c.req.url).pathname))
|
|
58
|
+
return c.json({ error: 'Hive is undergoing maintenance' }, 503); await next(); });
|
|
53
59
|
app.use('*', bodyLimit({ maxSize: 70 * 1024 * 1024 }));
|
|
54
60
|
for (const target of config.targets)
|
|
55
61
|
store.seedTarget(target);
|
|
@@ -60,11 +66,11 @@ app.use('*', async (c, next) => {
|
|
|
60
66
|
await next();
|
|
61
67
|
});
|
|
62
68
|
app.get('/', (c) => c.html(page));
|
|
63
|
-
app.get('/health/live', (c) => c.json({ status: 'ok', service: 'hive-skills' }));
|
|
69
|
+
app.get('/health/live', (c) => c.json({ ...metadata(releasePackageRoot), status: 'ok', service: 'hive-skills' }));
|
|
64
70
|
app.get('/healthz', (c) => {
|
|
65
71
|
try {
|
|
66
72
|
const health = store.readiness();
|
|
67
|
-
return c.json({ status: health.ready ? 'ok' : 'degraded', service: 'hive-skills', ...health }, health.ready ? 200 : 503);
|
|
73
|
+
return c.json({ ...metadata(releasePackageRoot), status: health.ready ? 'ok' : 'degraded', service: 'hive-skills', ...health }, health.ready ? 200 : 503);
|
|
68
74
|
}
|
|
69
75
|
catch {
|
|
70
76
|
return c.json({ status: 'degraded', service: 'hive-skills', reason: 'database_unavailable' }, 503);
|
|
@@ -179,6 +185,7 @@ app.post('/auth/logout', (c) => {
|
|
|
179
185
|
});
|
|
180
186
|
app.get('/auth/me', requireAuth(), (c) => c.json(c.get('auth')));
|
|
181
187
|
app.get('/admin/skills', requireAuth(true), (c) => c.json(store.listAllSkills()));
|
|
188
|
+
app.get('/admin/updates', requireAuth(true), async (c) => c.json(await updateStatus(metadata(releasePackageRoot).release, upgradeHome())));
|
|
182
189
|
app.post('/admin/skills', requireAuth(true), async (c) => {
|
|
183
190
|
const body = await requestJson(c);
|
|
184
191
|
const skill = store.createSkill({
|
package/dist/cli.js
CHANGED
|
@@ -4,11 +4,15 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import { packageRoot } from './paths.js';
|
|
5
5
|
const usage = `Hive Skills: HTTPS skill registry and Hermes distribution bridge.
|
|
6
6
|
|
|
7
|
-
Usage: hive-skills [start|seed|check|env-schema] [--env-file FILE]
|
|
7
|
+
Usage: hive-skills [start|seed|check|env-schema|migrate|upgrade|rollback] [--env-file FILE]
|
|
8
8
|
hive-skills --help | --version
|
|
9
9
|
|
|
10
10
|
start Start the registry (default); requires TLS key and certificate.
|
|
11
11
|
check Validate configuration, TLS and existing storage without writing.
|
|
12
|
+
--json prints a JSON report.
|
|
13
|
+
migrate --plan [--json] inspects; otherwise back up and migrate (--backup-dir DIR).
|
|
14
|
+
upgrade Delegate a complete managed bundle upgrade to the Hive launcher.
|
|
15
|
+
rollback Delegate managed code rollback or explicitly confirmed data restore.
|
|
12
16
|
env-schema Print configuration metadata as JSON without loading configuration.
|
|
13
17
|
seed Explicitly seed the bundled HiveMem skill; never runs on start.
|
|
14
18
|
--env-file Load a dotenv file; existing environment values win.
|
|
@@ -18,6 +22,27 @@ HIVE_SKILLS_ADMIN_USERS, HIVE_SKILLS_TLS_KEY_FILE and HIVE_SKILLS_TLS_CERT_FILE.
|
|
|
18
22
|
See the package README for the complete configuration and migration guide.`;
|
|
19
23
|
export async function main(args) {
|
|
20
24
|
try {
|
|
25
|
+
const special = args.find(arg => ['upgrade', 'rollback', 'migrate'].includes(arg));
|
|
26
|
+
if (special) {
|
|
27
|
+
const rest = [...args];
|
|
28
|
+
rest.splice(rest.indexOf(special), 1);
|
|
29
|
+
for (let i = 0; i < rest.length; i++) {
|
|
30
|
+
if (rest[i] === '--env-file' || rest[i]?.startsWith('--env-file=')) {
|
|
31
|
+
const inline = rest[i].startsWith('--env-file=');
|
|
32
|
+
const file = inline ? rest[i].slice(11) : rest[i + 1];
|
|
33
|
+
if (!file)
|
|
34
|
+
throw new Error('--env-file requires a file');
|
|
35
|
+
process.loadEnvFile(resolve(file));
|
|
36
|
+
rest.splice(i, inline ? 1 : 2);
|
|
37
|
+
i--;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (special === 'migrate') {
|
|
41
|
+
await (await import('./upgrade.js')).skillsMigration(rest);
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
return (await import('./release/runtime.js')).delegate([special, ...rest]);
|
|
45
|
+
}
|
|
21
46
|
if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
|
|
22
47
|
console.log(usage);
|
|
23
48
|
return 0;
|
|
@@ -44,6 +69,8 @@ export async function main(args) {
|
|
|
44
69
|
}
|
|
45
70
|
else if (!command && (arg === 'start' || arg === 'seed' || arg === 'check'))
|
|
46
71
|
command = arg;
|
|
72
|
+
else if (arg === '--json' && command === 'check')
|
|
73
|
+
continue;
|
|
47
74
|
else
|
|
48
75
|
throw new Error('Invalid arguments. Run hive-skills --help.');
|
|
49
76
|
}
|
|
@@ -57,10 +84,16 @@ export async function main(args) {
|
|
|
57
84
|
if (existsSync('.env'))
|
|
58
85
|
process.loadEnvFile('.env');
|
|
59
86
|
}
|
|
60
|
-
|
|
87
|
+
if (args.includes('--json'))
|
|
88
|
+
return (await import('./release/runtime.js')).captureCheck(() => checkConfiguration(process.env));
|
|
89
|
+
const code = checkConfiguration(process.env);
|
|
90
|
+
await (await import('./release/runtime.js')).printUpdateNotice(JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8')).version);
|
|
91
|
+
return code;
|
|
61
92
|
}
|
|
62
|
-
if (command === 'seed')
|
|
93
|
+
if (command === 'seed') {
|
|
94
|
+
await (await import('./upgrade.js')).skillsMigration([]);
|
|
63
95
|
await import('./seed.js');
|
|
96
|
+
}
|
|
64
97
|
else
|
|
65
98
|
await import('./server.js');
|
|
66
99
|
return 0;
|
package/dist/db.js
CHANGED
|
@@ -3,59 +3,9 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
4
|
import { config } from './config.js';
|
|
5
5
|
import { compatibleVersion } from './compatibility.js';
|
|
6
|
+
if (!existsSync(config.databasePath))
|
|
7
|
+
throw new Error('Skills database is missing; run hive-skills migrate before opening storage');
|
|
6
8
|
const db = new DatabaseSync(config.databasePath, { enableForeignKeyConstraints: true });
|
|
7
|
-
db.exec(`
|
|
8
|
-
PRAGMA journal_mode = WAL;
|
|
9
|
-
CREATE TABLE IF NOT EXISTS skills (
|
|
10
|
-
slug TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, homepage TEXT,
|
|
11
|
-
enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0, 1)), created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
12
|
-
) STRICT;
|
|
13
|
-
CREATE TABLE IF NOT EXISTS releases (
|
|
14
|
-
skill_slug TEXT NOT NULL REFERENCES skills(slug) ON DELETE CASCADE, version TEXT NOT NULL,
|
|
15
|
-
sha256 TEXT NOT NULL, archive_path TEXT NOT NULL, notes TEXT, created_at TEXT NOT NULL,
|
|
16
|
-
min_hermes_version TEXT, max_hermes_version TEXT,
|
|
17
|
-
PRIMARY KEY(skill_slug, version)
|
|
18
|
-
) STRICT;
|
|
19
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
20
|
-
id TEXT PRIMARY KEY, username TEXT NOT NULL, expires_at TEXT NOT NULL, created_at TEXT NOT NULL
|
|
21
|
-
) STRICT;
|
|
22
|
-
CREATE TABLE IF NOT EXISTS deployment_targets (
|
|
23
|
-
name TEXT PRIMARY KEY, host TEXT NOT NULL, username TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0, 1))
|
|
24
|
-
) STRICT;
|
|
25
|
-
CREATE TABLE IF NOT EXISTS channel_releases (
|
|
26
|
-
channel TEXT NOT NULL CHECK(channel IN ('stable','canary')), skill_slug TEXT NOT NULL REFERENCES skills(slug) ON DELETE CASCADE,
|
|
27
|
-
version TEXT NOT NULL, PRIMARY KEY(channel,skill_slug), FOREIGN KEY(skill_slug,version) REFERENCES releases(skill_slug,version)
|
|
28
|
-
) STRICT;
|
|
29
|
-
CREATE TABLE IF NOT EXISTS target_policies (
|
|
30
|
-
target TEXT PRIMARY KEY REFERENCES deployment_targets(name), channel TEXT NOT NULL CHECK(channel IN ('stable','canary')),
|
|
31
|
-
token_hash TEXT NOT NULL, skills_json TEXT, receipt_json TEXT, updated_at TEXT NOT NULL
|
|
32
|
-
) STRICT;
|
|
33
|
-
CREATE TABLE IF NOT EXISTS registry_migrations (id TEXT PRIMARY KEY) STRICT;
|
|
34
|
-
CREATE TABLE IF NOT EXISTS deployment_jobs (
|
|
35
|
-
id TEXT PRIMARY KEY, target TEXT NOT NULL, actor TEXT NOT NULL, status TEXT NOT NULL,
|
|
36
|
-
result TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
37
|
-
) STRICT;
|
|
38
|
-
CREATE TABLE IF NOT EXISTS audit_log (
|
|
39
|
-
id INTEGER PRIMARY KEY, actor TEXT NOT NULL, action TEXT NOT NULL, subject TEXT NOT NULL, details TEXT NOT NULL, created_at TEXT NOT NULL
|
|
40
|
-
) STRICT;
|
|
41
|
-
`);
|
|
42
|
-
const releaseColumns = new Set(db.prepare('PRAGMA table_info(releases)').all().map((column) => column.name));
|
|
43
|
-
if (!releaseColumns.has('min_hermes_version'))
|
|
44
|
-
db.exec('ALTER TABLE releases ADD COLUMN min_hermes_version TEXT');
|
|
45
|
-
if (!releaseColumns.has('max_hermes_version'))
|
|
46
|
-
db.exec('ALTER TABLE releases ADD COLUMN max_hermes_version TEXT');
|
|
47
|
-
// Preserve the currently published version during the first channel migration.
|
|
48
|
-
if (!db.prepare("SELECT id FROM registry_migrations WHERE id='release-channels'").get()) {
|
|
49
|
-
db.exec("BEGIN IMMEDIATE");
|
|
50
|
-
try {
|
|
51
|
-
db.exec("INSERT OR IGNORE INTO channel_releases SELECT 'stable', skill_slug, version FROM releases r WHERE created_at=(SELECT MAX(created_at) FROM releases WHERE skill_slug=r.skill_slug)");
|
|
52
|
-
db.exec("INSERT INTO registry_migrations VALUES ('release-channels'); COMMIT");
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
db.exec('ROLLBACK');
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
9
|
const tokenHash = (token) => createHash('sha256').update(token).digest('hex');
|
|
60
10
|
const now = () => new Date().toISOString();
|
|
61
11
|
const mapSkill = (row) => ({ slug: row.slug, name: row.name, description: row.description, homepage: row.homepage, enabled: row.enabled === 1, createdAt: row.created_at, updatedAt: row.updated_at });
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { config } from './config.js';
|
|
3
3
|
import { store } from './db.js';
|
|
4
|
+
import { maintenanceActive } from './release/runtime.js';
|
|
4
5
|
/** One local deployment at a time. Output may contain credentials: retain only exit codes. */
|
|
5
6
|
export function startDeploymentWorker() {
|
|
6
7
|
let busy = false;
|
|
8
|
+
let active = Promise.resolve();
|
|
7
9
|
store.recoverDeployments();
|
|
8
10
|
const timer = setInterval(() => {
|
|
9
|
-
if (busy)
|
|
11
|
+
if (busy || maintenanceActive('/worker'))
|
|
10
12
|
return;
|
|
11
13
|
const job = store.claimDeployment();
|
|
12
14
|
if (!job)
|
|
@@ -26,8 +28,10 @@ export function startDeploymentWorker() {
|
|
|
26
28
|
HIVE_SKILLS_TARGET_USER: target.username, HIVE_SKILLS_KEY_ONLY: 'true' },
|
|
27
29
|
});
|
|
28
30
|
let finished = false;
|
|
31
|
+
let complete = () => { };
|
|
32
|
+
active = new Promise((done) => { complete = done; });
|
|
29
33
|
const finish = (ok, code) => { if (finished)
|
|
30
|
-
return; finished = true; store.finishDeployment(job.id, ok, code); busy = false; };
|
|
34
|
+
return; finished = true; store.finishDeployment(job.id, ok, code); busy = false; complete(); };
|
|
31
35
|
child.once('error', () => finish(false, 'launch_failed'));
|
|
32
36
|
child.once('close', (code) => finish(code === 0, code === 0 ? 'timer_installed' : `exit_${code ?? 'signal'}`));
|
|
33
37
|
}
|
|
@@ -37,5 +41,5 @@ export function startDeploymentWorker() {
|
|
|
37
41
|
}
|
|
38
42
|
}, 1000);
|
|
39
43
|
timer.unref();
|
|
40
|
-
return () => clearInterval(timer);
|
|
44
|
+
return async () => { clearInterval(timer); await active; };
|
|
41
45
|
}
|
package/dist/env-schema.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { commonEnv } from './release/runtime.js';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { packageRoot } from './paths.js';
|
|
@@ -7,7 +8,7 @@ export function envSchemaDocument() {
|
|
|
7
8
|
return {
|
|
8
9
|
service: 'skills', package: '@hivemagnus/hive-skills', version, defaultPort: 4030,
|
|
9
10
|
healthPath: '/healthz', consolePath: '/', dependencies: [],
|
|
10
|
-
env: [
|
|
11
|
+
env: [...commonEnv(),
|
|
11
12
|
{ name: 'NODE_ENV', default: 'production', description: 'Runtime environment.' },
|
|
12
13
|
{ name: 'HIVE_SKILLS_HOST', default: '127.0.0.1', description: 'Listener bind address.' },
|
|
13
14
|
{ name: 'HIVE_SKILLS_PORT', default: '4030', description: 'HTTPS listener port.' },
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function initializeSchema(db) {
|
|
2
|
+
db.exec(`
|
|
3
|
+
PRAGMA journal_mode = WAL;
|
|
4
|
+
CREATE TABLE IF NOT EXISTS skills (
|
|
5
|
+
slug TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, homepage TEXT,
|
|
6
|
+
enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0, 1)), created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
7
|
+
) STRICT;
|
|
8
|
+
CREATE TABLE IF NOT EXISTS releases (
|
|
9
|
+
skill_slug TEXT NOT NULL REFERENCES skills(slug) ON DELETE CASCADE, version TEXT NOT NULL,
|
|
10
|
+
sha256 TEXT NOT NULL, archive_path TEXT NOT NULL, notes TEXT, created_at TEXT NOT NULL,
|
|
11
|
+
min_hermes_version TEXT, max_hermes_version TEXT,
|
|
12
|
+
PRIMARY KEY(skill_slug, version)
|
|
13
|
+
) STRICT;
|
|
14
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
15
|
+
id TEXT PRIMARY KEY, username TEXT NOT NULL, expires_at TEXT NOT NULL, created_at TEXT NOT NULL
|
|
16
|
+
) STRICT;
|
|
17
|
+
CREATE TABLE IF NOT EXISTS deployment_targets (
|
|
18
|
+
name TEXT PRIMARY KEY, host TEXT NOT NULL, username TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0, 1))
|
|
19
|
+
) STRICT;
|
|
20
|
+
CREATE TABLE IF NOT EXISTS channel_releases (
|
|
21
|
+
channel TEXT NOT NULL CHECK(channel IN ('stable','canary')), skill_slug TEXT NOT NULL REFERENCES skills(slug) ON DELETE CASCADE,
|
|
22
|
+
version TEXT NOT NULL, PRIMARY KEY(channel,skill_slug), FOREIGN KEY(skill_slug,version) REFERENCES releases(skill_slug,version)
|
|
23
|
+
) STRICT;
|
|
24
|
+
CREATE TABLE IF NOT EXISTS target_policies (
|
|
25
|
+
target TEXT PRIMARY KEY REFERENCES deployment_targets(name), channel TEXT NOT NULL CHECK(channel IN ('stable','canary')),
|
|
26
|
+
token_hash TEXT NOT NULL, skills_json TEXT, receipt_json TEXT, updated_at TEXT NOT NULL
|
|
27
|
+
) STRICT;
|
|
28
|
+
CREATE TABLE IF NOT EXISTS registry_migrations (id TEXT PRIMARY KEY) STRICT;
|
|
29
|
+
CREATE TABLE IF NOT EXISTS deployment_jobs (
|
|
30
|
+
id TEXT PRIMARY KEY, target TEXT NOT NULL, actor TEXT NOT NULL, status TEXT NOT NULL,
|
|
31
|
+
result TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
32
|
+
) STRICT;
|
|
33
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
34
|
+
id INTEGER PRIMARY KEY, actor TEXT NOT NULL, action TEXT NOT NULL, subject TEXT NOT NULL, details TEXT NOT NULL, created_at TEXT NOT NULL
|
|
35
|
+
) STRICT;
|
|
36
|
+
`);
|
|
37
|
+
const releaseColumns = new Set(db.prepare('PRAGMA table_info(releases)').all().map((column) => column.name));
|
|
38
|
+
if (!releaseColumns.has('min_hermes_version'))
|
|
39
|
+
db.exec('ALTER TABLE releases ADD COLUMN min_hermes_version TEXT');
|
|
40
|
+
if (!releaseColumns.has('max_hermes_version'))
|
|
41
|
+
db.exec('ALTER TABLE releases ADD COLUMN max_hermes_version TEXT');
|
|
42
|
+
// Preserve the currently published version during the first channel migration.
|
|
43
|
+
if (!db.prepare("SELECT id FROM registry_migrations WHERE id='release-channels'").get()) {
|
|
44
|
+
db.exec('BEGIN IMMEDIATE');
|
|
45
|
+
try {
|
|
46
|
+
db.exec("INSERT OR IGNORE INTO channel_releases SELECT 'stable', skill_slug, version FROM releases r WHERE created_at=(SELECT MAX(created_at) FROM releases WHERE skill_slug=r.skill_slug)");
|
|
47
|
+
db.exec("INSERT INTO registry_migrations VALUES ('release-channels'); COMMIT");
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
db.exec('ROLLBACK');
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Colin McDonnell
|
|
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.
|