@hasna/skills 0.1.71 → 0.1.72

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/migrate.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // @bun
3
3
 
4
4
  // src/server/migrate.ts
5
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
6
- import { join as join5 } from "path";
5
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
6
+ import { join as join7 } from "path";
7
7
 
8
8
  // src/lib/retired-settings.ts
9
9
  var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
@@ -81,65 +81,174 @@ function normalizePrefix(value) {
81
81
  }
82
82
 
83
83
  // src/server/database-url.ts
84
- import { isAbsolute, join as join2 } from "path";
84
+ import { isAbsolute, join as join4 } from "path";
85
85
  import { fileURLToPath } from "url";
86
86
 
87
87
  // src/lib/config.ts
88
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
89
- import { join, dirname } from "path";
88
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
89
+ import { join as join3, dirname } from "path";
90
+
91
+ // src/lib/app-home.ts
92
+ import { existsSync } from "fs";
93
+ import { homedir as homedir2 } from "os";
94
+ import { join as join2, resolve } from "path";
95
+
96
+ // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
90
97
  import { homedir } from "os";
98
+ import { join } from "path";
99
+ var KIND_ENV = {
100
+ config: "HASNA_CONFIG_HOME",
101
+ data: "HASNA_DATA_HOME",
102
+ state: "HASNA_STATE_HOME",
103
+ cache: "HASNA_CACHE_HOME"
104
+ };
105
+ var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
106
+ function assertApp(app) {
107
+ if (typeof app !== "string" || app.length === 0) {
108
+ throw new TypeError("paths: app must be a non-empty string");
109
+ }
110
+ if (!APP_SLUG_RE.test(app)) {
111
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
112
+ }
113
+ }
114
+ function envOf(options) {
115
+ return options.env ?? process.env;
116
+ }
117
+ function envValue(options, kind) {
118
+ const value = envOf(options)[KIND_ENV[kind]];
119
+ return typeof value === "string" && value.length > 0 ? value : undefined;
120
+ }
121
+ function isMacOS(platform) {
122
+ return platform === "darwin";
123
+ }
124
+ function baseDir(kind, options) {
125
+ const override = envValue(options, kind);
126
+ if (override)
127
+ return override;
128
+ const home = options.home ?? homedir();
129
+ const platform = options.platform ?? process.platform;
130
+ if (isMacOS(platform)) {
131
+ switch (kind) {
132
+ case "config":
133
+ case "data":
134
+ return join(home, "Library", "Application Support", "Hasna");
135
+ case "cache":
136
+ return join(home, "Library", "Caches", "Hasna");
137
+ case "state":
138
+ return join(home, "Library", "Logs", "Hasna");
139
+ }
140
+ }
141
+ switch (kind) {
142
+ case "config":
143
+ return join(home, ".config", "hasna");
144
+ case "data":
145
+ return join(home, ".local", "share", "hasna");
146
+ case "state":
147
+ return join(home, ".local", "state", "hasna");
148
+ case "cache":
149
+ return join(home, ".cache", "hasna");
150
+ }
151
+ }
152
+ function resolvePath(kind, options) {
153
+ assertApp(options.app);
154
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
155
+ return join(baseDir(kind, options), appSegment);
156
+ }
157
+ function dataDir(options) {
158
+ return resolvePath("data", options);
159
+ }
160
+
161
+ // src/lib/app-home.ts
162
+ var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
163
+ var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
164
+ var SKILLS_HOME_ENV = "SKILLS_HOME";
165
+ var DEFAULT_SQLITE_FILENAME = "server.db";
166
+ var GLOBAL_CONFIG_FILENAME = "config.json";
167
+ function effectiveHome() {
168
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir2() || "/tmp";
169
+ }
170
+ function legacyDataRoot() {
171
+ return join2(effectiveHome(), ".hasna", "skills");
172
+ }
173
+ function resolverDataRoot(home = effectiveHome()) {
174
+ return dataDir({ app: "skills", home });
175
+ }
176
+ function adoptResolverDataRoot(resolved, env = process.env) {
177
+ const dataOverride = env.HASNA_DATA_HOME;
178
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
179
+ return true;
180
+ return existsSync(join2(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join2(resolved, GLOBAL_CONFIG_FILENAME));
181
+ }
182
+ function exactDataRoot() {
183
+ for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
184
+ const dir = process.env[key]?.trim();
185
+ if (dir)
186
+ return resolve(dir);
187
+ }
188
+ return;
189
+ }
190
+ function hasExactOverride(env = process.env) {
191
+ return Boolean(env[DATA_DIR_ENV]?.trim()) || Boolean(env[HASNA_SKILLS_HOME_ENV]?.trim()) || Boolean(env[SKILLS_HOME_ENV]?.trim());
192
+ }
193
+ function hasOperatorOverride(env = process.env) {
194
+ return hasExactOverride(env) || Boolean(env.HASNA_DATA_HOME?.trim());
195
+ }
196
+ function getDataRoot() {
197
+ const exact = exactDataRoot();
198
+ if (exact)
199
+ return exact;
200
+ const resolved = resolverDataRoot();
201
+ return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
202
+ }
203
+ // src/lib/config.ts
91
204
  function mergeDirectoryContents(sourceDir, targetDir) {
92
- if (!existsSync(sourceDir))
205
+ if (!existsSync2(sourceDir))
93
206
  return;
94
207
  mkdirSync(targetDir, { recursive: true });
95
208
  for (const entry of readdirSync(sourceDir)) {
96
- const sourcePath = join(sourceDir, entry);
97
- const targetPath = join(targetDir, entry);
209
+ const sourcePath = join3(sourceDir, entry);
210
+ const targetPath = join3(targetDir, entry);
98
211
  try {
99
212
  const sourceStat = statSync(sourcePath);
100
213
  if (sourceStat.isDirectory()) {
101
214
  mergeDirectoryContents(sourcePath, targetPath);
102
215
  continue;
103
216
  }
104
- if (!existsSync(targetPath))
217
+ if (!existsSync2(targetPath))
105
218
  copyFileSync(sourcePath, targetPath);
106
219
  } catch {}
107
220
  }
108
221
  }
109
- var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
110
222
  function getDataDir() {
111
- const override = process.env[DATA_DIR_ENV];
112
- if (override) {
113
- try {
114
- mkdirSync(override, { recursive: true });
115
- } catch {}
116
- return override;
117
- }
118
- const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
119
- const newDir = join(home, ".hasna", "skills");
120
- const oldDir = join(home, ".skills");
121
- const oldConfigFile = join(home, ".skillsrc");
122
- mkdirSync(newDir, { recursive: true });
223
+ const root = getDataRoot();
224
+ try {
225
+ mkdirSync(root, { recursive: true });
226
+ } catch {}
227
+ if (hasOperatorOverride())
228
+ return root;
229
+ const home = effectiveHome();
230
+ const oldDir = join3(home, ".skills");
231
+ const oldConfigFile = join3(home, ".skillsrc");
123
232
  try {
124
- mergeDirectoryContents(oldDir, newDir);
233
+ mergeDirectoryContents(oldDir, root);
125
234
  } catch {}
126
- if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
235
+ if (existsSync2(oldConfigFile) && !existsSync2(join3(root, "config.json"))) {
127
236
  try {
128
- copyFileSync(oldConfigFile, join(newDir, "config.json"));
237
+ copyFileSync(oldConfigFile, join3(root, "config.json"));
129
238
  } catch {}
130
239
  }
131
- return newDir;
240
+ return root;
132
241
  }
133
242
 
134
243
  // src/server/database-url.ts
135
- var DEFAULT_SQLITE_FILENAME = "server.db";
244
+ var DEFAULT_SQLITE_FILENAME2 = "server.db";
136
245
  var SQLITE_MEMORY_PATH = ":memory:";
137
246
  var POSTGRES_SCHEMES = new Set(["postgres", "postgresql"]);
138
247
  var SQLITE_SCHEMES = new Set(["sqlite", "sqlite3", "file"]);
139
248
  var MEMORY_SCHEMES = new Set(["memory"]);
140
249
  var SQLITE_EXTENSIONS = [".db", ".sqlite", ".sqlite3", ".db3"];
141
250
  function defaultSqlitePath() {
142
- return join2(getDataDir(), DEFAULT_SQLITE_FILENAME);
251
+ return join4(getDataDir(), DEFAULT_SQLITE_FILENAME2);
143
252
  }
144
253
  function resolveDatabaseTarget(raw) {
145
254
  const value = raw?.trim();
@@ -166,7 +275,7 @@ function resolveDatabaseTarget(raw) {
166
275
  throw new Error(`unsupported database scheme "${scheme}:". Supported: postgres://, postgresql://, sqlite:, file:, ` + `an absolute or relative path to a .db/.sqlite file, ":memory:", or "memory:" (non-durable, tests only). ` + `Leave the setting empty to use the default SQLite database at ${defaultSqlitePath()}.`);
167
276
  }
168
277
  if (looksLikeSqlitePath(value)) {
169
- const path = isAbsolute(value) ? value : join2(process.cwd(), value);
278
+ const path = isAbsolute(value) ? value : join4(process.cwd(), value);
170
279
  return { kind: "sqlite", path, durable: true, label: `sqlite (${path})` };
171
280
  }
172
281
  throw new Error(`could not resolve a database backend from "${value}". Use postgres://\u2026, sqlite:\u2026, an absolute or ` + `relative path ending in ${SQLITE_EXTENSIONS.join("/")}, ":memory:", or leave it empty for the ` + `default SQLite database at ${defaultSqlitePath()}.`);
@@ -190,7 +299,7 @@ function sqlitePathFromUrl(value, scheme) {
190
299
  }
191
300
  return rest.slice(2).replace(/^\/\/+/, "/");
192
301
  }
193
- return isAbsolute(rest) ? rest : join2(process.cwd(), rest);
302
+ return isAbsolute(rest) ? rest : join4(process.cwd(), rest);
194
303
  }
195
304
  function looksLikeSqlitePath(value) {
196
305
  if (value.includes("/"))
@@ -199,16 +308,16 @@ function looksLikeSqlitePath(value) {
199
308
  }
200
309
 
201
310
  // src/server/migrations-dir.ts
202
- import { existsSync as existsSync2 } from "fs";
203
- import { dirname as dirname2, join as join3 } from "path";
311
+ import { existsSync as existsSync3 } from "fs";
312
+ import { dirname as dirname2, join as join5 } from "path";
204
313
  var MIGRATION_DIALECTS = ["postgres", "sqlite"];
205
314
  var MAX_WALK_UP = 6;
206
315
  function findMigrationsRoot(startDirs = defaultStartDirs()) {
207
316
  for (const start of startDirs) {
208
317
  let dir = start;
209
318
  for (let level = 0;level < MAX_WALK_UP; level += 1) {
210
- const candidate = join3(dir, "migrations");
211
- if (MIGRATION_DIALECTS.some((dialect) => existsSync2(join3(candidate, dialect))))
319
+ const candidate = join5(dir, "migrations");
320
+ if (MIGRATION_DIALECTS.some((dialect) => existsSync3(join5(candidate, dialect))))
212
321
  return candidate;
213
322
  const parent = dirname2(dir);
214
323
  if (parent === dir)
@@ -222,8 +331,8 @@ function resolveMigrationsDir(dialect, root = findMigrationsRoot()) {
222
331
  if (!root) {
223
332
  throw new Error(`could not locate the migrations directory for dialect "${dialect}". ` + `Expected a migrations/${dialect}/ folder alongside the package root.`);
224
333
  }
225
- const dir = join3(root, dialect);
226
- if (!existsSync2(dir)) {
334
+ const dir = join5(root, dialect);
335
+ if (!existsSync3(dir)) {
227
336
  throw new Error(`migrations directory not found: ${dir}`);
228
337
  }
229
338
  return dir;
@@ -240,7 +349,7 @@ function defaultStartDirs() {
240
349
  import { Database } from "bun:sqlite";
241
350
  import { randomUUID as randomUUID2 } from "crypto";
242
351
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
243
- import { dirname as dirname3, join as join4 } from "path";
352
+ import { dirname as dirname3, join as join6 } from "path";
244
353
 
245
354
  // src/server/auth.ts
246
355
  import { createHash } from "crypto";
@@ -1119,7 +1228,7 @@ function applySqliteMigrations(db, migrationsDir = resolveMigrationsDir("sqlite"
1119
1228
  const appliedNow = [];
1120
1229
  for (const file of files) {
1121
1230
  const version = file.replace(/\.sql$/, "");
1122
- const text = readFileSync2(join4(migrationsDir, file), "utf8");
1231
+ const text = readFileSync2(join6(migrationsDir, file), "utf8");
1123
1232
  const apply = db.transaction(() => {
1124
1233
  const already = db.query("SELECT 1 AS present FROM schema_migrations WHERE version = ? LIMIT 1").get(version);
1125
1234
  if (already)
@@ -1192,7 +1301,7 @@ async function runMigrations(databaseUrl, migrationsDir) {
1192
1301
  return { backend: "postgres", applied: await runPostgresMigrations(target.url, migrationsDir ?? resolveMigrationsDir("postgres")) };
1193
1302
  }
1194
1303
  async function runPostgresMigrations(databaseUrl, migrationsDir) {
1195
- if (!existsSync3(migrationsDir))
1304
+ if (!existsSync4(migrationsDir))
1196
1305
  throw new Error(`migrations directory not found: ${migrationsDir}`);
1197
1306
  const bunWithSql = Bun;
1198
1307
  const sql = new bunWithSql.SQL(databaseUrl, { max: 1 });
@@ -1208,7 +1317,7 @@ async function runPostgresMigrations(databaseUrl, migrationsDir) {
1208
1317
  const version = file.replace(/\.sql$/, "");
1209
1318
  if (applied.has(version))
1210
1319
  continue;
1211
- const sqlText = readFileSync3(join5(migrationsDir, file), "utf8");
1320
+ const sqlText = readFileSync3(join7(migrationsDir, file), "utf8");
1212
1321
  await sql.unsafe("BEGIN");
1213
1322
  try {
1214
1323
  await sql.unsafe(sqlText);