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.
- package/dist/bin/gemi.js +500 -16
- package/dist/bin/gemi.js.map +10 -5
- package/dist/{chunk-n412aa9s.js → chunk-khf9xda6.js} +1 -1
- package/dist/{chunk-gfma8e03.js → chunk-x8beq9c4.js} +1 -1
- package/dist/chunk-y3zz410b.js +6 -0
- package/dist/{chunk-txhcx69q.js.map → chunk-y3zz410b.js.map} +2 -2
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +3 -3
- package/dist/server/index.js +1 -1
- package/package.json +3 -2
- package/skills/gemi-react-best-practices/SKILL.md +231 -0
- package/skills/gemi-react-best-practices/rules/_sections.md +56 -0
- package/skills/gemi-react-best-practices/rules/_template.md +28 -0
- package/skills/gemi-react-best-practices/rules/bundle-deep-imports.md +48 -0
- package/skills/gemi-react-best-practices/rules/bundle-mount-gate-heavy-panels.md +63 -0
- package/skills/gemi-react-best-practices/rules/client-form-vs-mutation-hooks.md +57 -0
- package/skills/gemi-react-best-practices/rules/client-loading-error-exports.md +54 -0
- package/skills/gemi-react-best-practices/rules/client-no-effect-data-flow.md +68 -0
- package/skills/gemi-react-best-practices/rules/client-typed-links.md +51 -0
- package/skills/gemi-react-best-practices/rules/controller-authorize-every-tenant-read.md +65 -0
- package/skills/gemi-react-best-practices/rules/controller-parse-request-at-the-boundary.md +54 -0
- package/skills/gemi-react-best-practices/rules/controller-redirect-facade-throws.md +68 -0
- package/skills/gemi-react-best-practices/rules/controller-request-schema.md +61 -0
- package/skills/gemi-react-best-practices/rules/controller-throw-framework-errors.md +57 -0
- package/skills/gemi-react-best-practices/rules/i18n-define-dictionary-inline.md +58 -0
- package/skills/gemi-react-best-practices/rules/orm-analytics-connection.md +53 -0
- package/skills/gemi-react-best-practices/rules/orm-include-not-n-plus-one.md +56 -0
- package/skills/gemi-react-best-practices/rules/orm-paginate-helper.md +69 -0
- package/skills/gemi-react-best-practices/rules/orm-plain-rows-by-default.md +55 -0
- package/skills/gemi-react-best-practices/rules/orm-select-narrow.md +58 -0
- package/skills/gemi-react-best-practices/rules/orm-transaction-no-io.md +54 -0
- package/skills/gemi-react-best-practices/rules/orm-transaction-sequential.md +64 -0
- package/skills/gemi-react-best-practices/rules/payload-dont-overprefetch.md +54 -0
- package/skills/gemi-react-best-practices/rules/payload-instant-vs-prefetch.md +58 -0
- package/skills/gemi-react-best-practices/rules/payload-minimal-view-props.md +51 -0
- package/skills/gemi-react-best-practices/rules/payload-parallel-controller-work.md +56 -0
- package/skills/gemi-react-best-practices/rules/payload-prefetch-late-queries.md +58 -0
- package/skills/gemi-react-best-practices/rules/payload-prefetch-mirrors-usequery.md +52 -0
- package/skills/gemi-react-best-practices/rules/query-debounce-search-variant.md +52 -0
- package/skills/gemi-react-best-practices/rules/query-keep-previous-data.md +40 -0
- package/skills/gemi-react-best-practices/rules/query-lazy-vs-mount-gate.md +54 -0
- package/skills/gemi-react-best-practices/rules/query-mutate-over-refetch.md +55 -0
- package/skills/gemi-react-best-practices/rules/query-no-hand-rolled-fetch.md +60 -0
- package/skills/gemi-react-best-practices/rules/query-revalidate-on-focus.md +44 -0
- package/skills/gemi-react-best-practices/rules/query-share-cache-key.md +51 -0
- package/skills/gemi-react-best-practices/rules/query-suspense-default.md +52 -0
- package/skills/gemi-react-best-practices/rules/routing-cache-policy-constants.md +53 -0
- package/skills/gemi-react-best-practices/rules/routing-middleware-dsl.md +60 -0
- package/skills/gemi-react-best-practices/rules/routing-resource-routes.md +59 -0
- package/skills/gemi-react-best-practices/rules/routing-routers-are-classes.md +55 -0
- package/skills/gemi-react-best-practices/rules/service-lazy-not-module-scope.md +63 -0
- package/skills/gemi-react-best-practices/rules/service-queue-is-in-memory.md +52 -0
- package/skills/gemi-react-best-practices/rules/service-static-token-and-name.md +52 -0
- package/skills/gemi-react-best-practices/rules/structure-discovered-vs-registered.md +71 -0
- package/skills/gemi-react-best-practices/rules/structure-do-not-reinvent-the-framework.md +58 -0
- package/skills/gemi-react-best-practices/rules/testing-assert-behaviour-over-markup.md +54 -0
- package/skills/gemi-react-best-practices/rules/testing-match-the-suite.md +57 -0
- package/skills/gemi-react-best-practices/rules/testing-page-seeds-real-inputs.md +65 -0
- package/dist/chunk-txhcx69q.js +0 -6
- /package/dist/{chunk-n412aa9s.js.map → chunk-khf9xda6.js.map} +0 -0
- /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
|
|
35929
|
-
import { existsSync as
|
|
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 =
|
|
39782
|
-
return
|
|
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 =
|
|
39787
|
-
const appDir =
|
|
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
|
-
`${
|
|
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 =
|
|
39816
|
-
const appDir =
|
|
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 =
|
|
39853
|
-
const appDir =
|
|
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 =
|
|
39871
|
-
const appDir =
|
|
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 =
|
|
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:
|
|
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=
|
|
40425
|
+
//# debugId=A013F2EE91F38DEE64756E2164756E21
|
|
39942
40426
|
//# sourceMappingURL=gemi.js.map
|