gemi 0.59.0 → 0.60.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.
Files changed (63) hide show
  1. package/dist/bin/gemi.js +500 -16
  2. package/dist/bin/gemi.js.map +10 -5
  3. package/dist/{chunk-n412aa9s.js → chunk-khf9xda6.js} +1 -1
  4. package/dist/{chunk-gfma8e03.js → chunk-x8beq9c4.js} +1 -1
  5. package/dist/chunk-y3zz410b.js +6 -0
  6. package/dist/{chunk-txhcx69q.js.map → chunk-y3zz410b.js.map} +2 -2
  7. package/dist/config/index.d.ts +2 -0
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/index.js +2 -2
  10. package/dist/config/index.js.map +3 -3
  11. package/dist/server/index.js +1 -1
  12. package/package.json +3 -2
  13. package/skills/gemi-react-best-practices/SKILL.md +231 -0
  14. package/skills/gemi-react-best-practices/rules/_sections.md +56 -0
  15. package/skills/gemi-react-best-practices/rules/_template.md +28 -0
  16. package/skills/gemi-react-best-practices/rules/bundle-deep-imports.md +48 -0
  17. package/skills/gemi-react-best-practices/rules/bundle-mount-gate-heavy-panels.md +63 -0
  18. package/skills/gemi-react-best-practices/rules/client-form-vs-mutation-hooks.md +57 -0
  19. package/skills/gemi-react-best-practices/rules/client-loading-error-exports.md +54 -0
  20. package/skills/gemi-react-best-practices/rules/client-no-effect-data-flow.md +68 -0
  21. package/skills/gemi-react-best-practices/rules/client-typed-links.md +51 -0
  22. package/skills/gemi-react-best-practices/rules/controller-authorize-every-tenant-read.md +65 -0
  23. package/skills/gemi-react-best-practices/rules/controller-parse-request-at-the-boundary.md +54 -0
  24. package/skills/gemi-react-best-practices/rules/controller-redirect-facade-throws.md +68 -0
  25. package/skills/gemi-react-best-practices/rules/controller-request-schema.md +61 -0
  26. package/skills/gemi-react-best-practices/rules/controller-throw-framework-errors.md +57 -0
  27. package/skills/gemi-react-best-practices/rules/i18n-define-dictionary-inline.md +58 -0
  28. package/skills/gemi-react-best-practices/rules/orm-analytics-connection.md +53 -0
  29. package/skills/gemi-react-best-practices/rules/orm-include-not-n-plus-one.md +56 -0
  30. package/skills/gemi-react-best-practices/rules/orm-paginate-helper.md +69 -0
  31. package/skills/gemi-react-best-practices/rules/orm-plain-rows-by-default.md +55 -0
  32. package/skills/gemi-react-best-practices/rules/orm-select-narrow.md +58 -0
  33. package/skills/gemi-react-best-practices/rules/orm-transaction-no-io.md +54 -0
  34. package/skills/gemi-react-best-practices/rules/orm-transaction-sequential.md +64 -0
  35. package/skills/gemi-react-best-practices/rules/payload-dont-overprefetch.md +54 -0
  36. package/skills/gemi-react-best-practices/rules/payload-instant-vs-prefetch.md +58 -0
  37. package/skills/gemi-react-best-practices/rules/payload-minimal-view-props.md +51 -0
  38. package/skills/gemi-react-best-practices/rules/payload-parallel-controller-work.md +56 -0
  39. package/skills/gemi-react-best-practices/rules/payload-prefetch-late-queries.md +58 -0
  40. package/skills/gemi-react-best-practices/rules/payload-prefetch-mirrors-usequery.md +52 -0
  41. package/skills/gemi-react-best-practices/rules/query-debounce-search-variant.md +52 -0
  42. package/skills/gemi-react-best-practices/rules/query-keep-previous-data.md +40 -0
  43. package/skills/gemi-react-best-practices/rules/query-lazy-vs-mount-gate.md +54 -0
  44. package/skills/gemi-react-best-practices/rules/query-mutate-over-refetch.md +55 -0
  45. package/skills/gemi-react-best-practices/rules/query-no-hand-rolled-fetch.md +60 -0
  46. package/skills/gemi-react-best-practices/rules/query-revalidate-on-focus.md +44 -0
  47. package/skills/gemi-react-best-practices/rules/query-share-cache-key.md +51 -0
  48. package/skills/gemi-react-best-practices/rules/query-suspense-default.md +52 -0
  49. package/skills/gemi-react-best-practices/rules/routing-cache-policy-constants.md +53 -0
  50. package/skills/gemi-react-best-practices/rules/routing-middleware-dsl.md +60 -0
  51. package/skills/gemi-react-best-practices/rules/routing-resource-routes.md +59 -0
  52. package/skills/gemi-react-best-practices/rules/routing-routers-are-classes.md +55 -0
  53. package/skills/gemi-react-best-practices/rules/service-lazy-not-module-scope.md +63 -0
  54. package/skills/gemi-react-best-practices/rules/service-queue-is-in-memory.md +52 -0
  55. package/skills/gemi-react-best-practices/rules/service-static-token-and-name.md +52 -0
  56. package/skills/gemi-react-best-practices/rules/structure-discovered-vs-registered.md +71 -0
  57. package/skills/gemi-react-best-practices/rules/structure-do-not-reinvent-the-framework.md +58 -0
  58. package/skills/gemi-react-best-practices/rules/testing-assert-behaviour-over-markup.md +54 -0
  59. package/skills/gemi-react-best-practices/rules/testing-match-the-suite.md +57 -0
  60. package/skills/gemi-react-best-practices/rules/testing-page-seeds-real-inputs.md +65 -0
  61. package/dist/chunk-txhcx69q.js +0 -6
  62. /package/dist/{chunk-n412aa9s.js.map → chunk-khf9xda6.js.map} +0 -0
  63. /package/dist/{chunk-gfma8e03.js.map → chunk-x8beq9c4.js.map} +0 -0
package/dist/bin/gemi.js CHANGED
@@ -35925,8 +35925,8 @@ var require_commander = __commonJS((exports) => {
35925
35925
  });
35926
35926
 
35927
35927
  // bin/gemi.ts
35928
- import path4 from "path";
35929
- import { existsSync as existsSync5 } from "fs";
35928
+ import path7 from "path";
35929
+ import { existsSync as existsSync8 } from "fs";
35930
35930
 
35931
35931
  // bin/createRollupInput.ts
35932
35932
  var {Glob } = globalThis.Bun;
@@ -39776,15 +39776,484 @@ class ApiManifestGenerator {
39776
39776
  }
39777
39777
  }
39778
39778
 
39779
+ // bin/version.ts
39780
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
39781
+ import path4 from "path";
39782
+ var VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
39783
+ function parseVersion(version) {
39784
+ const match = VERSION_RE.exec(version.trim());
39785
+ if (!match)
39786
+ return null;
39787
+ const [, major, minor, patch, prerelease] = match;
39788
+ return {
39789
+ core: [Number(major), Number(minor), Number(patch)],
39790
+ prerelease: prerelease ? prerelease.split(".") : []
39791
+ };
39792
+ }
39793
+ var NUMERIC_RE = /^\d+$/;
39794
+ function comparePrereleaseIdentifiers(a, b) {
39795
+ const aNumeric = NUMERIC_RE.test(a);
39796
+ const bNumeric = NUMERIC_RE.test(b);
39797
+ if (aNumeric && bNumeric)
39798
+ return Number(a) - Number(b);
39799
+ if (aNumeric)
39800
+ return -1;
39801
+ if (bNumeric)
39802
+ return 1;
39803
+ return a < b ? -1 : a > b ? 1 : 0;
39804
+ }
39805
+ function compareVersions(a, b) {
39806
+ const left = parseVersion(a);
39807
+ const right = parseVersion(b);
39808
+ if (!left || !right)
39809
+ return 0;
39810
+ for (let i = 0;i < 3; i++) {
39811
+ const diff = left.core[i] - right.core[i];
39812
+ if (diff !== 0)
39813
+ return diff;
39814
+ }
39815
+ if (left.prerelease.length === 0 && right.prerelease.length === 0)
39816
+ return 0;
39817
+ if (left.prerelease.length === 0)
39818
+ return 1;
39819
+ if (right.prerelease.length === 0)
39820
+ return -1;
39821
+ const shared = Math.min(left.prerelease.length, right.prerelease.length);
39822
+ for (let i = 0;i < shared; i++) {
39823
+ const diff = comparePrereleaseIdentifiers(left.prerelease[i], right.prerelease[i]);
39824
+ if (diff !== 0)
39825
+ return diff;
39826
+ }
39827
+ return left.prerelease.length - right.prerelease.length;
39828
+ }
39829
+ function channelFor(version) {
39830
+ const parsed = parseVersion(version);
39831
+ const first = parsed?.prerelease[0];
39832
+ return first && !NUMERIC_RE.test(first) ? first : "latest";
39833
+ }
39834
+ function findGemiPackageDir(rootDir) {
39835
+ let dir = path4.resolve(rootDir);
39836
+ while (true) {
39837
+ const candidate = path4.join(dir, "node_modules", "gemi");
39838
+ if (existsSync5(path4.join(candidate, "package.json")))
39839
+ return candidate;
39840
+ const parent = path4.dirname(dir);
39841
+ if (parent === dir)
39842
+ return null;
39843
+ dir = parent;
39844
+ }
39845
+ }
39846
+ function readInstalledVersion(rootDir) {
39847
+ const packageDir = findGemiPackageDir(rootDir);
39848
+ if (!packageDir)
39849
+ return null;
39850
+ try {
39851
+ const manifest = JSON.parse(readFileSync2(path4.join(packageDir, "package.json"), "utf8"));
39852
+ return typeof manifest?.version === "string" ? manifest.version : null;
39853
+ } catch {
39854
+ return null;
39855
+ }
39856
+ }
39857
+
39858
+ // bin/registry.ts
39859
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
39860
+ async function fetchPublishedVersion(tag, options = {}) {
39861
+ const {
39862
+ timeoutMs = 2000,
39863
+ registry = process.env.GEMI_REGISTRY ?? DEFAULT_REGISTRY,
39864
+ fetchImpl = fetch
39865
+ } = options;
39866
+ const controller = new AbortController;
39867
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
39868
+ try {
39869
+ const response = await fetchImpl(`${registry.replace(/\/+$/, "")}/gemi/${encodeURIComponent(tag)}`, {
39870
+ signal: controller.signal,
39871
+ headers: { accept: "application/json" }
39872
+ });
39873
+ if (!response.ok)
39874
+ return null;
39875
+ const body = await response.json();
39876
+ return typeof body?.version === "string" ? body.version : null;
39877
+ } catch {
39878
+ return null;
39879
+ } finally {
39880
+ clearTimeout(timer);
39881
+ }
39882
+ }
39883
+ async function fetchNewestPublished(installed, options = {}) {
39884
+ const channel = channelFor(installed);
39885
+ if (channel === "latest")
39886
+ return fetchPublishedVersion("latest", options);
39887
+ const [onChannel, onLatest] = await Promise.all([
39888
+ fetchPublishedVersion(channel, options),
39889
+ fetchPublishedVersion("latest", options)
39890
+ ]);
39891
+ if (!onChannel)
39892
+ return onLatest;
39893
+ if (!onLatest)
39894
+ return onChannel;
39895
+ return compareVersions(onLatest, onChannel) > 0 ? onLatest : onChannel;
39896
+ }
39897
+
39898
+ // bin/update-check.ts
39899
+ function shouldCheckForUpdate(env) {
39900
+ if (env.GEMI_NO_UPDATE_CHECK)
39901
+ return false;
39902
+ if (env.CI)
39903
+ return false;
39904
+ return true;
39905
+ }
39906
+ function formatUpdateNotice(installed, published) {
39907
+ if (compareVersions(published, installed) <= 0)
39908
+ return null;
39909
+ const from = parseVersion(installed);
39910
+ const to = parseVersion(published);
39911
+ const breaking = !!from && !!to && (from.core[0] !== to.core[0] || from.core[1] !== to.core[1]);
39912
+ return [
39913
+ `
39914
+ Update available: gemi ${installed} \u2192 ${published}`,
39915
+ ` Run \`gemi upgrade\` to update.`,
39916
+ ...breaking ? [
39917
+ ` This crosses a minor version \u2014 read UPGRADE.md before you do:`,
39918
+ ` https://github.com/nstfkc/gemi/blob/main/UPGRADE.md`
39919
+ ] : [],
39920
+ ""
39921
+ ].join(`
39922
+ `);
39923
+ }
39924
+ async function checkForUpdate(options) {
39925
+ const { rootDir, env = process.env, timeoutMs, fetchImpl } = options;
39926
+ if (!shouldCheckForUpdate(env))
39927
+ return null;
39928
+ const installed = readInstalledVersion(rootDir);
39929
+ if (!installed)
39930
+ return null;
39931
+ const published = await fetchNewestPublished(installed, {
39932
+ timeoutMs,
39933
+ fetchImpl
39934
+ });
39935
+ if (!published)
39936
+ return null;
39937
+ return formatUpdateNotice(installed, published);
39938
+ }
39939
+ function reportUpdate(options, log = console.log) {
39940
+ return checkForUpdate(options).then((notice) => {
39941
+ if (notice)
39942
+ log(notice);
39943
+ }).catch(() => {});
39944
+ }
39945
+
39946
+ // bin/upgrade.ts
39947
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
39948
+ import path6 from "path";
39949
+
39950
+ // bin/install-skill.ts
39951
+ import { createHash as createHash2 } from "crypto";
39952
+ import {
39953
+ cpSync,
39954
+ existsSync as existsSync6,
39955
+ mkdirSync as mkdirSync2,
39956
+ readFileSync as readFileSync3,
39957
+ readdirSync as readdirSync3,
39958
+ renameSync,
39959
+ rmSync as rmSync2,
39960
+ writeFileSync as writeFileSync2
39961
+ } from "fs";
39962
+ import path5 from "path";
39963
+ var SKILL_NAME = "gemi-react-best-practices";
39964
+ function skillSourceDir(gemiPackageDir) {
39965
+ return path5.join(gemiPackageDir, "skills", SKILL_NAME);
39966
+ }
39967
+ function skillTargetDir(rootDir) {
39968
+ return path5.join(rootDir, ".agents", "skills", SKILL_NAME);
39969
+ }
39970
+ function lockPath(rootDir) {
39971
+ return path5.join(rootDir, ".agents", ".skill-lock.json");
39972
+ }
39973
+ function isSkillInstalled(rootDir) {
39974
+ return existsSync6(path5.join(skillTargetDir(rootDir), "SKILL.md"));
39975
+ }
39976
+ function hashDirectory(dir) {
39977
+ const hash = createHash2("sha1");
39978
+ const walk3 = (current, prefix) => {
39979
+ const entries = readdirSync3(current, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
39980
+ for (const entry of entries) {
39981
+ const full = path5.join(current, entry.name);
39982
+ const relative3 = prefix ? `${prefix}/${entry.name}` : entry.name;
39983
+ if (entry.isDirectory()) {
39984
+ walk3(full, relative3);
39985
+ } else if (entry.isFile()) {
39986
+ hash.update(relative3);
39987
+ hash.update("\x00");
39988
+ hash.update(readFileSync3(full));
39989
+ hash.update("\x00");
39990
+ }
39991
+ }
39992
+ };
39993
+ walk3(dir, "");
39994
+ return hash.digest("hex");
39995
+ }
39996
+ function readLock(rootDir) {
39997
+ const empty = { version: 3, skills: {} };
39998
+ const file = lockPath(rootDir);
39999
+ if (!existsSync6(file))
40000
+ return empty;
40001
+ try {
40002
+ const parsed = JSON.parse(readFileSync3(file, "utf8"));
40003
+ if (!parsed || typeof parsed !== "object")
40004
+ return empty;
40005
+ return {
40006
+ ...parsed,
40007
+ version: typeof parsed.version === "number" ? parsed.version : 3,
40008
+ skills: parsed.skills && typeof parsed.skills === "object" ? parsed.skills : {}
40009
+ };
40010
+ } catch {
40011
+ return empty;
40012
+ }
40013
+ }
40014
+ function writeLock(rootDir, lock) {
40015
+ mkdirSync2(path5.dirname(lockPath(rootDir)), { recursive: true });
40016
+ writeFileSync2(lockPath(rootDir), `${JSON.stringify(lock, null, 2)}
40017
+ `);
40018
+ }
40019
+ function quarantineUnreadableLock(rootDir) {
40020
+ const file = lockPath(rootDir);
40021
+ if (!existsSync6(file))
40022
+ return null;
40023
+ try {
40024
+ const parsed = JSON.parse(readFileSync3(file, "utf8"));
40025
+ if (parsed && typeof parsed === "object")
40026
+ return null;
40027
+ } catch {}
40028
+ const quarantined = `${file}.corrupt`;
40029
+ renameSync(file, quarantined);
40030
+ return quarantined;
40031
+ }
40032
+ function installSkill(options) {
40033
+ const { error = console.error } = options;
40034
+ try {
40035
+ return install(options);
40036
+ } catch (cause) {
40037
+ error(`Could not install the \`${SKILL_NAME}\` skill: ` + `${cause instanceof Error ? cause.message : String(cause)}`);
40038
+ return 1;
40039
+ }
40040
+ }
40041
+ function install(options) {
40042
+ const {
40043
+ rootDir,
40044
+ force = false,
40045
+ onlyIfInstalled = false,
40046
+ log = console.log,
40047
+ error = console.error,
40048
+ now = () => new Date().toISOString()
40049
+ } = options;
40050
+ if (onlyIfInstalled && !isSkillInstalled(rootDir))
40051
+ return 0;
40052
+ const packageDir = findGemiPackageDir(rootDir);
40053
+ if (!packageDir) {
40054
+ error(`Could not find an installed gemi under ${rootDir}. Run this from the ` + `root of a gemi project (one with \`gemi\` in its node_modules).`);
40055
+ return 1;
40056
+ }
40057
+ const source = skillSourceDir(packageDir);
40058
+ if (!existsSync6(path5.join(source, "SKILL.md"))) {
40059
+ error(`The installed gemi does not ship the \`${SKILL_NAME}\` skill (looked in ` + `${source}). It was added in a later release \u2014 run \`gemi upgrade\` first.`);
40060
+ return 1;
40061
+ }
40062
+ const target = skillTargetDir(rootDir);
40063
+ const lock = readLock(rootDir);
40064
+ const recorded = lock.skills[SKILL_NAME];
40065
+ const existing = existsSync6(target) ? hashDirectory(target) : null;
40066
+ if (existing !== null && !force) {
40067
+ if (!recorded) {
40068
+ error(`${path5.relative(rootDir, target) || target} already exists and was not ` + `installed by \`gemi install-skill\`. Re-run with --force to replace it.`);
40069
+ return 1;
40070
+ }
40071
+ if (recorded.skillFolderHash !== existing) {
40072
+ error(`${path5.relative(rootDir, target) || target} has local edits. Re-run with ` + `--force to discard them and install the version shipped with gemi.`);
40073
+ return 1;
40074
+ }
40075
+ }
40076
+ const version = readInstalledVersion(rootDir) ?? undefined;
40077
+ const sourceHash = hashDirectory(source);
40078
+ if (existing !== null && existing === sourceHash) {
40079
+ log(`${SKILL_NAME} is already up to date${version ? ` (gemi ${version})` : ""}.`);
40080
+ return 0;
40081
+ }
40082
+ mkdirSync2(path5.dirname(target), { recursive: true });
40083
+ const staging = `${target}.tmp-${process.pid}`;
40084
+ rmSync2(staging, { recursive: true, force: true });
40085
+ try {
40086
+ cpSync(source, staging, { recursive: true });
40087
+ rmSync2(target, { recursive: true, force: true });
40088
+ renameSync(staging, target);
40089
+ } finally {
40090
+ rmSync2(staging, { recursive: true, force: true });
40091
+ }
40092
+ const timestamp = now();
40093
+ lock.skills[SKILL_NAME] = {
40094
+ source: "gemi",
40095
+ sourceType: "npm",
40096
+ skillPath: `skills/${SKILL_NAME}/SKILL.md`,
40097
+ skillFolderHash: sourceHash,
40098
+ ...version ? { version } : {},
40099
+ installedAt: recorded?.installedAt ?? timestamp,
40100
+ updatedAt: timestamp
40101
+ };
40102
+ const quarantined = quarantineUnreadableLock(rootDir);
40103
+ if (quarantined) {
40104
+ log(`${path5.relative(rootDir, lockPath(rootDir))} could not be parsed and was ` + `kept as ${path5.basename(quarantined)}; a fresh one was written.`);
40105
+ }
40106
+ writeLock(rootDir, lock);
40107
+ const where = path5.relative(rootDir, target) || target;
40108
+ log(existing === null ? `Installed ${SKILL_NAME} to ${where}${version ? ` (gemi ${version})` : ""}.` : `Updated ${SKILL_NAME} in ${where}${version ? ` (gemi ${version})` : ""}.`);
40109
+ return 0;
40110
+ }
40111
+
40112
+ // bin/upgrade.ts
40113
+ var LOCKFILES = [
40114
+ ["bun", ["bun.lock", "bun.lockb"]],
40115
+ ["pnpm", ["pnpm-lock.yaml"]],
40116
+ ["yarn", ["yarn.lock"]],
40117
+ ["npm", ["package-lock.json"]]
40118
+ ];
40119
+ function detectPackageManager(rootDir) {
40120
+ let dir = path6.resolve(rootDir);
40121
+ while (true) {
40122
+ for (const [manager, files] of LOCKFILES) {
40123
+ if (files.some((file) => existsSync7(path6.join(dir, file))))
40124
+ return manager;
40125
+ }
40126
+ const parent = path6.dirname(dir);
40127
+ if (parent === dir)
40128
+ return "bun";
40129
+ dir = parent;
40130
+ }
40131
+ }
40132
+ function dependencyKind(dir) {
40133
+ const manifestPath = path6.join(dir, "package.json");
40134
+ if (!existsSync7(manifestPath))
40135
+ return null;
40136
+ try {
40137
+ const manifest = JSON.parse(readFileSync4(manifestPath, "utf8"));
40138
+ if (manifest?.dependencies?.gemi)
40139
+ return "dependencies";
40140
+ if (manifest?.devDependencies?.gemi)
40141
+ return "devDependencies";
40142
+ return null;
40143
+ } catch {
40144
+ return null;
40145
+ }
40146
+ }
40147
+ function findDeclaringDir(rootDir) {
40148
+ let dir = path6.resolve(rootDir);
40149
+ while (true) {
40150
+ const kind = dependencyKind(dir);
40151
+ if (kind)
40152
+ return { dir, kind };
40153
+ const parent = path6.dirname(dir);
40154
+ if (parent === dir)
40155
+ return null;
40156
+ dir = parent;
40157
+ }
40158
+ }
40159
+ function installCommand(manager, spec, kind) {
40160
+ const dev = kind === "devDependencies";
40161
+ switch (manager) {
40162
+ case "bun":
40163
+ return ["bun", "add", ...dev ? ["--dev"] : [], spec];
40164
+ case "pnpm":
40165
+ return ["pnpm", "add", ...dev ? ["--save-dev"] : [], spec];
40166
+ case "yarn":
40167
+ return ["yarn", "add", ...dev ? ["--dev"] : [], spec];
40168
+ case "npm":
40169
+ return ["npm", "install", ...dev ? ["--save-dev"] : ["--save-prod"], spec];
40170
+ }
40171
+ }
40172
+ async function defaultSpawn(cmd, cwd) {
40173
+ const proc = Bun.spawn({
40174
+ cmd,
40175
+ cwd,
40176
+ stdout: "inherit",
40177
+ stderr: "inherit",
40178
+ stdin: "inherit"
40179
+ });
40180
+ await proc.exited;
40181
+ return proc.exitCode ?? 1;
40182
+ }
40183
+ async function runUpgrade(options) {
40184
+ const {
40185
+ rootDir,
40186
+ target,
40187
+ dryRun = false,
40188
+ fetchImpl,
40189
+ log = console.log,
40190
+ error = console.error,
40191
+ spawn = defaultSpawn,
40192
+ installSkillImpl = installSkill
40193
+ } = options;
40194
+ const installed = readInstalledVersion(rootDir);
40195
+ if (!installed) {
40196
+ error(`Could not find an installed gemi under ${rootDir}. Run this from the ` + `root of a gemi project (one with \`gemi\` in its node_modules).`);
40197
+ return 1;
40198
+ }
40199
+ const declaring = findDeclaringDir(rootDir);
40200
+ if (!declaring) {
40201
+ error(`No \`package.json\` at or above ${rootDir} declares \`gemi\` as a ` + `dependency, so there is nothing to upgrade. Run this from the ` + `directory whose manifest depends on gemi.`);
40202
+ return 1;
40203
+ }
40204
+ const { dir: installDir, kind } = declaring;
40205
+ const explicitVersion = target && parseVersion(target) ? target : null;
40206
+ const tag = explicitVersion ? null : target;
40207
+ let resolved = explicitVersion;
40208
+ if (!resolved) {
40209
+ log(`Resolving gemi@${tag ?? channelFor(installed)}...`);
40210
+ resolved = tag ? await fetchPublishedVersion(tag, { timeoutMs: 1e4, fetchImpl }) : await fetchNewestPublished(installed, { timeoutMs: 1e4, fetchImpl });
40211
+ if (!resolved) {
40212
+ error(`Could not resolve \`gemi@${tag ?? channelFor(installed)}\` from the npm ` + `registry. Check your connection, or pass an explicit version: ` + `\`gemi upgrade 0.59.0\`.`);
40213
+ return 1;
40214
+ }
40215
+ }
40216
+ const direction = compareVersions(resolved, installed);
40217
+ if (direction === 0) {
40218
+ log(explicitVersion ? `gemi is already at ${installed}.` : tag ? `gemi ${installed} is already the newest release on \`${tag}\`.` : `gemi ${installed} is already the newest release published.`);
40219
+ return 0;
40220
+ }
40221
+ const spec = `gemi@${resolved}`;
40222
+ const cmd = installCommand(detectPackageManager(installDir), spec, kind);
40223
+ log(direction > 0 ? `Upgrading gemi ${installed} \u2192 ${resolved}` : `Downgrading gemi ${installed} \u2192 ${resolved}`);
40224
+ const from = parseVersion(installed);
40225
+ const to = parseVersion(resolved);
40226
+ if (from && to && (from.core[0] !== to.core[0] || from.core[1] !== to.core[1])) {
40227
+ log(`
40228
+ This crosses a minor version. UPGRADE.md documents what each one ` + `requires \u2014 read it before running your app:
40229
+ ` + ` https://github.com/nstfkc/gemi/blob/main/UPGRADE.md
40230
+ `);
40231
+ }
40232
+ if (dryRun) {
40233
+ log(`Would run: ${cmd.join(" ")}`);
40234
+ return 0;
40235
+ }
40236
+ const code = await spawn(cmd, installDir);
40237
+ if (code !== 0) {
40238
+ error(`
40239
+ \`${cmd.join(" ")}\` failed with exit code ${code}.`);
40240
+ return code;
40241
+ }
40242
+ log(`
40243
+ gemi is now ${resolved}.`);
40244
+ installSkillImpl({ rootDir, onlyIfInstalled: true, log, error });
40245
+ return 0;
40246
+ }
40247
+
39779
40248
  // bin/gemi.ts
39780
40249
  function appPreloadArgs(appDir) {
39781
- const preloadPath = path4.join(appDir, "preload.ts");
39782
- return existsSync5(preloadPath) ? ["--preload", preloadPath] : [];
40250
+ const preloadPath = path7.join(appDir, "preload.ts");
40251
+ return existsSync8(preloadPath) ? ["--preload", preloadPath] : [];
39783
40252
  }
39784
40253
  program.command("dev").action(async () => {
39785
40254
  console.log("Starting dev server...");
39786
- const rootDir = path4.resolve(process.cwd());
39787
- const appDir = path4.join(rootDir, "app");
40255
+ const rootDir = path7.resolve(process.cwd());
40256
+ const appDir = path7.join(rootDir, "app");
39788
40257
  process.env.NODE_ENV = "development";
39789
40258
  Bun.spawn({
39790
40259
  cmd: [
@@ -39794,11 +40263,12 @@ program.command("dev").action(async () => {
39794
40263
  "--preload",
39795
40264
  "gemi/bun/preload",
39796
40265
  ...appPreloadArgs(appDir),
39797
- `${path4.join(appDir, "server.ts")}`
40266
+ `${path7.join(appDir, "server.ts")}`
39798
40267
  ],
39799
40268
  stdout: "inherit",
39800
40269
  stderr: "inherit"
39801
40270
  });
40271
+ reportUpdate({ rootDir });
39802
40272
  });
39803
40273
  program.command("build").action(async () => {
39804
40274
  if (process.env.GEMI_BUILD_PROD !== "1") {
@@ -39812,8 +40282,8 @@ program.command("build").action(async () => {
39812
40282
  process.exit(proc.exitCode ?? 0);
39813
40283
  }
39814
40284
  process.env.NODE_ENV = "production";
39815
- const rootDir = path4.resolve(process.cwd());
39816
- const appDir = path4.join(rootDir, "app");
40285
+ const rootDir = path7.resolve(process.cwd());
40286
+ const appDir = path7.join(rootDir, "app");
39817
40287
  const config = await loadGemiConfig(rootDir);
39818
40288
  const input = await createRollupInput_default(appDir);
39819
40289
  process.env.GEMI_INPUT = JSON.stringify(input);
@@ -39849,8 +40319,8 @@ program.command("build").action(async () => {
39849
40319
  });
39850
40320
  program.command("start").action(async () => {
39851
40321
  console.log("Starting server...");
39852
- const rootDir = path4.resolve(process.cwd());
39853
- const appDir = path4.join(rootDir, "app");
40322
+ const rootDir = path7.resolve(process.cwd());
40323
+ const appDir = path7.join(rootDir, "app");
39854
40324
  const proc = Bun.spawn({
39855
40325
  cmd: [
39856
40326
  "bun",
@@ -39867,8 +40337,8 @@ program.command("start").action(async () => {
39867
40337
  });
39868
40338
  program.enablePositionalOptions();
39869
40339
  program.command("run").description("Run a Command from app/commands. Everything after the name is the " + "command's own, including its flags. Omit the name to list them").argument("[name]", "The command's name").argument("[args...]", "Passed through to the command untouched").passThroughOptions().action(async (name, args) => {
39870
- const rootDir = path4.resolve(process.cwd());
39871
- const appDir = path4.join(rootDir, "app");
40340
+ const rootDir = path7.resolve(process.cwd());
40341
+ const appDir = path7.join(rootDir, "app");
39872
40342
  let entry;
39873
40343
  try {
39874
40344
  entry = Bun.resolveSync("gemi/console/run", rootDir);
@@ -39895,10 +40365,24 @@ program.command("run").description("Run a Command from app/commands. Everything
39895
40365
  process.exit(proc.exitCode ?? 1);
39896
40366
  });
39897
40367
  program.command("migrate").description("Migrate an app from the 0.42 service-provider layout to the config + " + "container layout, flagging APIs retired since").option("--dry-run", "Print the plan without writing anything").action(async (options) => {
39898
- const rootDir = path4.resolve(process.cwd());
40368
+ const rootDir = path7.resolve(process.cwd());
39899
40369
  console.log(options.dryRun ? `Planning migration of ${rootDir}...` : `Migrating ${rootDir}...`);
39900
40370
  await runMigrate({ rootDir, dryRun: options.dryRun });
39901
40371
  });
40372
+ program.command("upgrade").description("Upgrade the `gemi` package. With no argument it moves to the newest " + "release on the channel you are already on \u2014 `rc` for a release " + "candidate, `latest` otherwise \u2014 so an rc is never 'upgraded' backwards " + "onto the older stable release").argument("[target]", "A version (`0.59.0`) to pin or roll back to, or a dist-tag (`rc`, " + "`latest`). Defaults to the current channel's tag").option("--dry-run", "Resolve the version and print the install command, run nothing").action(async (target, options) => {
40373
+ const code = await runUpgrade({
40374
+ rootDir: path7.resolve(process.cwd()),
40375
+ target,
40376
+ dryRun: options.dryRun
40377
+ });
40378
+ process.exit(code);
40379
+ });
40380
+ program.command("install-skill").description(`Install the \`${SKILL_NAME}\` agent skill that ships with your gemi into ` + `.agents/skills/. \`gemi upgrade\` re-runs this afterwards, so the rules an ` + `agent reads stay pinned to the gemi the app actually runs`).option("--force", "Replace an installed copy that has local edits, or one this command did not write").action(async (options) => {
40381
+ process.exit(installSkill({
40382
+ rootDir: path7.resolve(process.cwd()),
40383
+ force: options.force
40384
+ }));
40385
+ });
39902
40386
  var check = program.command("check").description("Checks that can be run in CI, on a project that is not running");
39903
40387
  check.command("models").description("Report policied model classes that the modules `Kernel.models` declares " + "do not register \u2014 the leak `Kernel.models` cannot see, because it can " + "only audit the modules it is handed. Imports every file it walks, so a " + "model file that does work when imported does it here; use --ignore").option("--dir <path>", "Model directory to walk, relative to the project root", "app/models").option("--models <paths>", "Module paths to register from, instead of reading `Kernel.models` \u2014 for " + "a Kernel whose import graph needs build-time transforms this command " + "cannot apply. Comma-separated, and repeatable", (value, previous) => [
39904
40388
  ...previous,
@@ -39909,7 +40393,7 @@ check.command("models").description("Report policied model classes that the modu
39909
40393
  ], []).action(async (options) => {
39910
40394
  try {
39911
40395
  const report2 = await checkModels({
39912
- rootDir: path4.resolve(process.cwd()),
40396
+ rootDir: path7.resolve(process.cwd()),
39913
40397
  modelsDir: options.dir,
39914
40398
  ignore: options.ignore,
39915
40399
  models: options.models
@@ -39938,5 +40422,5 @@ program.command("app:route-manifest").action(async () => {
39938
40422
  });
39939
40423
  program.parse();
39940
40424
 
39941
- //# debugId=FD52AD3855B4935264756E2164756E21
40425
+ //# debugId=A013F2EE91F38DEE64756E2164756E21
39942
40426
  //# sourceMappingURL=gemi.js.map