oh-my-space 0.14.0 → 0.14.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 +13 -5
- package/dist/build-info.json +1 -1
- package/dist/oms.js +182 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ OMS automates routine, deterministic preparation and bounded recovery whenever i
|
|
|
21
21
|
|
|
22
22
|
- [Node.js](https://nodejs.org) `>=20.19.0` to run `oms`.
|
|
23
23
|
- git `>=2.40` for `git switch` and the submodule commands `oms` relies on.
|
|
24
|
-
-
|
|
24
|
+
- Submodule commands require `oms.yaml` at the root Git top-level. `oms init` can scaffold outside Git, but run `git init` at that same workspace root before syncing sources.
|
|
25
25
|
|
|
26
26
|
## Install
|
|
27
27
|
|
|
@@ -70,6 +70,14 @@ oms/
|
|
|
70
70
|
|
|
71
71
|
`oms.yaml` is your declaration, `.gitmodules` and each `oms/<alias>` gitlink are tracked in your project history, and every directory under `oms/` is a normal checked-out repository you can branch, edit, and commit in.
|
|
72
72
|
|
|
73
|
+
## Command locations and workspace discovery
|
|
74
|
+
|
|
75
|
+
Workspace-aware commands search from the current directory toward the filesystem root and use the nearest `oms.yaml`. The first `oms.yaml` entry encountered is authoritative. It must be a regular file, or a symbolic link whose target is a regular file. A directory, broken link, link to a non-file target, or invalid nearest manifest causes the command to fail; OMS does not skip it and fall back to an outer workspace.
|
|
76
|
+
|
|
77
|
+
You can run workspace-aware commands at the workspace root or below it. When the current directory is inside `oms/<alias>/` for an alias declared by the selected manifest, `oms status --json` reports that alias and commands such as `oms commit` and `oms record` can infer it. An alias supplied on the command line always takes precedence over the inferred current alias. Arbitrary descendants and undeclared `oms/<name>/` paths can discover the workspace but do not become a current alias.
|
|
78
|
+
|
|
79
|
+
Commands that inspect or change submodule state require the selected manifest directory to be the root Git top-level. A nested `oms.yaml` below an enclosing Git root is rejected before `.gitmodules`, the root index, `oms/`, or the manifest can change. Repair that layout by either moving `oms.yaml` to the enclosing Git root or initializing a separate Git repository at the intended nested workspace root. `oms sync --list` is manifest-only and remains available before Git initialization.
|
|
80
|
+
|
|
73
81
|
## Typical branch flow
|
|
74
82
|
|
|
75
83
|
Start a branch, commit inside the submodule, push it, then record the pointer move in your project history. Each command stays in a single Git scope:
|
|
@@ -183,12 +191,12 @@ Skill firing is best-effort — an agent loads a skill only when it judges the s
|
|
|
183
191
|
|
|
184
192
|
| Command | Runs in | Does | Notes |
|
|
185
193
|
| --- | --- | --- | --- |
|
|
186
|
-
| `oms init` | current directory | Writes a starter `oms.yaml`. |
|
|
187
|
-
| `oms doctor` |
|
|
194
|
+
| `oms init` | current directory | Writes a starter `oms.yaml`. | The directory must be outside Git or the root Git top-level. A nested Git-work-tree directory is rejected before writes, even with `--force`. Otherwise refuses an existing manifest unless `--force` is used. Does not gitignore `oms/`. |
|
|
195
|
+
| `oms doctor` | workspace root or child path | Checks the nearest `oms.yaml`, Git root identity, and each alias's submodule state. | Diagnoses a nested manifest directly instead of treating it as a valid root. Returns exit 2 if any warning is raised. |
|
|
188
196
|
| `oms sync <alias>` / `--all` | workspace root | Registers missing repos with `git submodule add`, initializes registered-but-uninitialized ones, fetches, and attaches the baseline branch. | Reproduces the recorded pointer on a fresh clone. Topology changes (`.gitmodules`, `oms/<alias>`) are left unstaged by default; commit them via the prompt or `--commit` (`chore(oms): add ...`). |
|
|
189
197
|
| `oms status [alias...]` / `--all` | anywhere under root | Prints branch, pointer state (`ok`/`moved`/`uninit`/`missing`/`conflict`), dirtiness, and ahead/behind for each submodule. | `moved` means the working commit differs from the recorded pointer — record it with `oms record`. `--json` prints one machine-readable object on stdout for tooling and agents. |
|
|
190
|
-
| `oms commit [alias]` | workspace root or inside `oms/<alias>/` | Commits source changes inside the selected submodule only; never the root gitlink. | `-m <message>` is required (repeatable). Commits existing staged changes as-is, otherwise stages all with `git add -A`. Infers
|
|
191
|
-
| `oms record [alias]` | workspace root or inside `oms/<alias>/` | Commits an existing root gitlink pointer update for one alias (`chore(oms): update <alias> submodule to <sha>`). | Root repo only, path-limited to `oms/<alias>`; refuses unrelated staged changes. Not for adds/removals — use `oms sync`/`oms unsync`. |
|
|
198
|
+
| `oms commit [alias]` | workspace root or inside `oms/<alias>/` | Commits source changes inside the selected submodule only; never the root gitlink. | `-m <message>` is required (repeatable). Commits existing staged changes as-is, otherwise stages all with `git add -A`. Infers a configured alias from the current `oms/<alias>/` directory; an explicit alias wins. |
|
|
199
|
+
| `oms record [alias]` | workspace root or inside `oms/<alias>/` | Commits an existing root gitlink pointer update for one alias (`chore(oms): update <alias> submodule to <sha>`). | Root repo only, path-limited to `oms/<alias>`; refuses unrelated staged changes. An explicit alias wins over current-path inference. Not for adds/removals — use `oms sync`/`oms unsync`. |
|
|
192
200
|
| `oms branch switch [alias] [branch]` | workspace root | `git switch` to a LOCAL branch, creating it locally if it does not exist yet (no remote required). | `--from <ref>` sets the start point for a new branch. Omit alias/branch to pick interactively (or create a new branch). |
|
|
193
201
|
| `oms branch checkout [alias] [branch]` | workspace root | `git fetch origin --prune`, then check out a REMOTE branch (`origin/*`) as a local tracking branch (or switch to an existing local counterpart). | Omit alias/branch to pick interactively. To create a brand-new local branch, use `oms branch switch`. |
|
|
194
202
|
| `oms branch list [alias]` | workspace root | Safely prepares one declared submodule, refreshes all declared remotes, and lists local and remote-tracking branches. | Shows baseline certainty, current/baseline flags, exact upstream divergence, detached HEAD, and per-remote `fresh`/`stale`/`unavailable` state. Never switches a branch or changes a root gitlink outside an explicitly accepted `oms sync`. |
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"9ab3e6b650acac13d9fff4b8b091b07721d427ac"}
|
package/dist/oms.js
CHANGED
|
@@ -14197,7 +14197,7 @@ import { join as join3 } from "node:path";
|
|
|
14197
14197
|
|
|
14198
14198
|
// scripts/lib/git.ts
|
|
14199
14199
|
import { spawnSync } from "node:child_process";
|
|
14200
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
14200
|
+
import { existsSync as existsSync2, lstatSync, realpathSync, statSync } from "node:fs";
|
|
14201
14201
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
14202
14202
|
function redactSensitiveUrls(value) {
|
|
14203
14203
|
return value.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+(?::[^\s/@]*)?@/gi, "$1[redacted]@").replace(/([?&](?:(?:access|auth|bearer|id|oauth|refresh)?[_-]?token|(?:api|private)?[_-]?key|auth(?:orization)?|(?:client|consumer)?[_-]?secret|credential|jwt|pass(?:word|wd)?|signature)=)[^&\s]+/gi, "$1[redacted]");
|
|
@@ -14241,15 +14241,54 @@ function isGitVersionSupported(v2) {
|
|
|
14241
14241
|
return v2.minor >= MIN_GIT_MINOR;
|
|
14242
14242
|
}
|
|
14243
14243
|
__name(isGitVersionSupported, "isGitVersionSupported");
|
|
14244
|
-
function
|
|
14244
|
+
function resolveWorkspaceManifest(options = {}) {
|
|
14245
14245
|
let current = resolve2(options.cwd ?? process.cwd());
|
|
14246
14246
|
while (true) {
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14247
|
+
const manifestPath = join2(current, MANIFEST_FILENAME);
|
|
14248
|
+
let entry;
|
|
14249
|
+
try {
|
|
14250
|
+
entry = lstatSync(manifestPath);
|
|
14251
|
+
} catch (error) {
|
|
14252
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
14253
|
+
if (code === "ENOENT") {
|
|
14254
|
+
const parent = dirname2(current);
|
|
14255
|
+
if (parent === current) return { kind: "missing" };
|
|
14256
|
+
current = parent;
|
|
14257
|
+
continue;
|
|
14258
|
+
}
|
|
14259
|
+
return {
|
|
14260
|
+
kind: "invalid",
|
|
14261
|
+
manifestPath,
|
|
14262
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
14263
|
+
};
|
|
14264
|
+
}
|
|
14265
|
+
try {
|
|
14266
|
+
if (entry.isFile() || entry.isSymbolicLink() && statSync(manifestPath).isFile()) {
|
|
14267
|
+
return { kind: "found", manifestPath, repoRoot: current };
|
|
14268
|
+
}
|
|
14269
|
+
} catch {
|
|
14270
|
+
return {
|
|
14271
|
+
kind: "invalid",
|
|
14272
|
+
manifestPath,
|
|
14273
|
+
reason: "it is a broken symbolic link or does not resolve to a regular file"
|
|
14274
|
+
};
|
|
14275
|
+
}
|
|
14276
|
+
return {
|
|
14277
|
+
kind: "invalid",
|
|
14278
|
+
manifestPath,
|
|
14279
|
+
reason: "it is not a regular file or a symbolic link to a regular file"
|
|
14280
|
+
};
|
|
14251
14281
|
}
|
|
14252
14282
|
}
|
|
14283
|
+
__name(resolveWorkspaceManifest, "resolveWorkspaceManifest");
|
|
14284
|
+
function findWorkspaceRoot(options = {}) {
|
|
14285
|
+
const resolution = resolveWorkspaceManifest(options);
|
|
14286
|
+
if (resolution.kind === "found") return resolution.repoRoot;
|
|
14287
|
+
if (resolution.kind === "missing") return null;
|
|
14288
|
+
throw new Error(
|
|
14289
|
+
`Found ${MANIFEST_FILENAME} at ${resolution.manifestPath}, but ${resolution.reason}. Replace that entry with a regular file; OMS will not fall back to an ancestor manifest.`
|
|
14290
|
+
);
|
|
14291
|
+
}
|
|
14253
14292
|
__name(findWorkspaceRoot, "findWorkspaceRoot");
|
|
14254
14293
|
function aliasDir(repoRoot, alias) {
|
|
14255
14294
|
return join2(repoRoot, DATA_DIRNAME, alias);
|
|
@@ -14259,10 +14298,34 @@ function submodulePath(alias) {
|
|
|
14259
14298
|
return `${DATA_DIRNAME}/${alias}`;
|
|
14260
14299
|
}
|
|
14261
14300
|
__name(submodulePath, "submodulePath");
|
|
14262
|
-
function
|
|
14263
|
-
|
|
14301
|
+
function inspectWorkspaceGitIdentity(workspaceRoot) {
|
|
14302
|
+
const topLevel = runGit(workspaceRoot, ["rev-parse", "--show-toplevel"], false, {
|
|
14303
|
+
...process.env,
|
|
14304
|
+
LANG: "C",
|
|
14305
|
+
LC_ALL: "C"
|
|
14306
|
+
});
|
|
14307
|
+
if (!topLevel.success) {
|
|
14308
|
+
const diagnostic = topLevel.stderr.trim();
|
|
14309
|
+
if (/not a git repository|not a git work tree/i.test(diagnostic)) return { kind: "no-work-tree" };
|
|
14310
|
+
return {
|
|
14311
|
+
kind: "indeterminate",
|
|
14312
|
+
reason: diagnostic || "Git did not return a repository top-level"
|
|
14313
|
+
};
|
|
14314
|
+
}
|
|
14315
|
+
const gitTopLevel = process.platform === "win32" ? topLevel.stdout.replace(/\r?\n$/, "") : topLevel.stdout.replace(/\n$/, "");
|
|
14316
|
+
if (!gitTopLevel) {
|
|
14317
|
+
return { kind: "indeterminate", reason: "Git returned an empty repository top-level" };
|
|
14318
|
+
}
|
|
14319
|
+
try {
|
|
14320
|
+
return realpathSync(workspaceRoot) === realpathSync(gitTopLevel) ? { kind: "match", gitTopLevel } : { kind: "mismatch", gitTopLevel };
|
|
14321
|
+
} catch (error) {
|
|
14322
|
+
return {
|
|
14323
|
+
kind: "indeterminate",
|
|
14324
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
14325
|
+
};
|
|
14326
|
+
}
|
|
14264
14327
|
}
|
|
14265
|
-
__name(
|
|
14328
|
+
__name(inspectWorkspaceGitIdentity, "inspectWorkspaceGitIdentity");
|
|
14266
14329
|
function registeredSubmodulePaths(repoRoot) {
|
|
14267
14330
|
if (!existsSync2(join2(repoRoot, ".gitmodules"))) return [];
|
|
14268
14331
|
const result = runGit(repoRoot, [
|
|
@@ -14650,6 +14713,7 @@ function abortOnLegacyRenameAt(repoRoot) {
|
|
|
14650
14713
|
}
|
|
14651
14714
|
__name(abortOnLegacyRenameAt, "abortOnLegacyRenameAt");
|
|
14652
14715
|
function emitLegacyRenameHintWalkUp(options = {}) {
|
|
14716
|
+
if (resolveWorkspaceManifest(options).kind !== "missing") return false;
|
|
14653
14717
|
let current = resolve3(options.cwd ?? process.cwd());
|
|
14654
14718
|
while (true) {
|
|
14655
14719
|
if (existsSync4(join4(current, LEGACY_MANIFEST))) {
|
|
@@ -14676,22 +14740,51 @@ function abortOnLegacyWorktree(repoRoot, repos) {
|
|
|
14676
14740
|
}
|
|
14677
14741
|
__name(abortOnLegacyWorktree, "abortOnLegacyWorktree");
|
|
14678
14742
|
function loadRepos(options = {}) {
|
|
14679
|
-
const
|
|
14680
|
-
if (
|
|
14743
|
+
const resolution = resolveWorkspaceManifest(options);
|
|
14744
|
+
if (resolution.kind === "missing") {
|
|
14681
14745
|
R2.error(
|
|
14682
14746
|
`Could not find ${MANIFEST_FILENAME} in the current directory or its parents. Create a ${MANIFEST_FILENAME} in this project, then retry.`
|
|
14683
14747
|
);
|
|
14684
14748
|
return null;
|
|
14685
14749
|
}
|
|
14750
|
+
if (resolution.kind === "invalid") {
|
|
14751
|
+
R2.error(
|
|
14752
|
+
`Found ${MANIFEST_FILENAME} at ${resolution.manifestPath}, but ${resolution.reason}. Replace that entry with a regular file; OMS will not fall back to an ancestor manifest.`
|
|
14753
|
+
);
|
|
14754
|
+
return null;
|
|
14755
|
+
}
|
|
14686
14756
|
try {
|
|
14687
|
-
|
|
14688
|
-
|
|
14757
|
+
return {
|
|
14758
|
+
repos: validateSources((0, import_yaml.parse)(readFileSync3(resolution.manifestPath, "utf8"))),
|
|
14759
|
+
repoRoot: resolution.repoRoot
|
|
14760
|
+
};
|
|
14689
14761
|
} catch (e2) {
|
|
14690
14762
|
R2.error(e2 instanceof Error ? e2.message : String(e2));
|
|
14691
14763
|
return null;
|
|
14692
14764
|
}
|
|
14693
14765
|
}
|
|
14694
14766
|
__name(loadRepos, "loadRepos");
|
|
14767
|
+
function emitWorkspaceGitIdentityError(repoRoot) {
|
|
14768
|
+
const identity = inspectWorkspaceGitIdentity(repoRoot);
|
|
14769
|
+
if (identity.kind === "match") return false;
|
|
14770
|
+
if (identity.kind === "no-work-tree") {
|
|
14771
|
+
R2.error(
|
|
14772
|
+
`${repoRoot} is not a git repository. oh-my-space manages sources as git submodules; run "git init" at the workspace root first.`
|
|
14773
|
+
);
|
|
14774
|
+
return true;
|
|
14775
|
+
}
|
|
14776
|
+
if (identity.kind === "mismatch") {
|
|
14777
|
+
R2.error(
|
|
14778
|
+
`Workspace manifest directory ${repoRoot} does not match the root Git top-level ${identity.gitTopLevel}. Move ${MANIFEST_FILENAME} to ${identity.gitTopLevel}, or initialize a separate Git repository at ${repoRoot}.`
|
|
14779
|
+
);
|
|
14780
|
+
return true;
|
|
14781
|
+
}
|
|
14782
|
+
R2.error(
|
|
14783
|
+
`Could not verify that workspace ${repoRoot} is the root Git top-level: ${identity.reason}. Retry after the workspace path and Git repository are accessible.`
|
|
14784
|
+
);
|
|
14785
|
+
return true;
|
|
14786
|
+
}
|
|
14787
|
+
__name(emitWorkspaceGitIdentityError, "emitWorkspaceGitIdentityError");
|
|
14695
14788
|
function loadForSubmodules() {
|
|
14696
14789
|
const loaded = loadRepos();
|
|
14697
14790
|
if (!loaded) {
|
|
@@ -14700,19 +14793,14 @@ function loadForSubmodules() {
|
|
|
14700
14793
|
}
|
|
14701
14794
|
const { repoRoot, repos } = loaded;
|
|
14702
14795
|
if (abortOnLegacyRenameAt(repoRoot)) return null;
|
|
14703
|
-
if (
|
|
14704
|
-
R2.error(
|
|
14705
|
-
`${repoRoot} is not a git repository. oh-my-space 0.6.0 manages sources as git submodules; run "git init" at the workspace root first.`
|
|
14706
|
-
);
|
|
14707
|
-
return null;
|
|
14708
|
-
}
|
|
14796
|
+
if (emitWorkspaceGitIdentityError(repoRoot)) return null;
|
|
14709
14797
|
if (abortOnLegacyWorktree(repoRoot, repos)) return null;
|
|
14710
14798
|
return loaded;
|
|
14711
14799
|
}
|
|
14712
14800
|
__name(loadForSubmodules, "loadForSubmodules");
|
|
14713
14801
|
|
|
14714
14802
|
// scripts/lib/status.ts
|
|
14715
|
-
import { existsSync as existsSync5, lstatSync, readdirSync } from "node:fs";
|
|
14803
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync } from "node:fs";
|
|
14716
14804
|
import { relative, resolve as resolve4 } from "node:path";
|
|
14717
14805
|
function pinState(repoRoot, alias) {
|
|
14718
14806
|
const r = runGit(repoRoot, ["submodule", "status", "--", submodulePath(alias)]);
|
|
@@ -14883,7 +14971,7 @@ __name(partialRemovalTopology, "partialRemovalTopology");
|
|
|
14883
14971
|
function readAliasDirEntries(repoRoot, alias) {
|
|
14884
14972
|
const dir = aliasDir(repoRoot, alias);
|
|
14885
14973
|
try {
|
|
14886
|
-
if (!
|
|
14974
|
+
if (!lstatSync2(dir).isDirectory()) return { exists: true, kind: "file" };
|
|
14887
14975
|
return { exists: true, kind: "dir", entries: readdirSync(dir) };
|
|
14888
14976
|
} catch (error) {
|
|
14889
14977
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -15305,7 +15393,7 @@ import {
|
|
|
15305
15393
|
constants as fsConstants,
|
|
15306
15394
|
copyFileSync,
|
|
15307
15395
|
fsyncSync,
|
|
15308
|
-
lstatSync as
|
|
15396
|
+
lstatSync as lstatSync3,
|
|
15309
15397
|
mkdirSync as mkdirSync2,
|
|
15310
15398
|
openSync as openSync2,
|
|
15311
15399
|
readFileSync as readFileSync6,
|
|
@@ -15318,7 +15406,7 @@ import {
|
|
|
15318
15406
|
import { dirname as dirname4, join as join7 } from "node:path";
|
|
15319
15407
|
|
|
15320
15408
|
// scripts/lib/gitmodules-reconcile.ts
|
|
15321
|
-
import { chmodSync, closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync5, renameSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
15409
|
+
import { chmodSync, closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync5, renameSync, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
15322
15410
|
import { join as join6 } from "node:path";
|
|
15323
15411
|
function shq(arg) {
|
|
15324
15412
|
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
@@ -15401,7 +15489,7 @@ function reconcileGitmodules(repoRoot, plans, snapshot) {
|
|
|
15401
15489
|
if (changedFields.size === 0) return { ok: true, changedFields };
|
|
15402
15490
|
let originalMode;
|
|
15403
15491
|
try {
|
|
15404
|
-
originalMode =
|
|
15492
|
+
originalMode = statSync2(gitmodules).mode & 511;
|
|
15405
15493
|
} catch {
|
|
15406
15494
|
return { ok: false, retained: "original", reason: "could not read .gitmodules file mode", unapplied: aliases };
|
|
15407
15495
|
}
|
|
@@ -15447,7 +15535,7 @@ __name(gitDir, "gitDir");
|
|
|
15447
15535
|
function omsStateDir(repoRoot) {
|
|
15448
15536
|
const dir = join7(gitDir(repoRoot), "oms");
|
|
15449
15537
|
if (pathExists(dir)) {
|
|
15450
|
-
const stat =
|
|
15538
|
+
const stat = lstatSync3(dir);
|
|
15451
15539
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error(`unsafe OMS state directory: ${dir}`);
|
|
15452
15540
|
} else {
|
|
15453
15541
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
@@ -15461,7 +15549,7 @@ function markerPath(repoRoot) {
|
|
|
15461
15549
|
__name(markerPath, "markerPath");
|
|
15462
15550
|
function pathExists(path) {
|
|
15463
15551
|
try {
|
|
15464
|
-
|
|
15552
|
+
lstatSync3(path);
|
|
15465
15553
|
return true;
|
|
15466
15554
|
} catch (error) {
|
|
15467
15555
|
if (error.code === "ENOENT") return false;
|
|
@@ -15471,7 +15559,7 @@ function pathExists(path) {
|
|
|
15471
15559
|
__name(pathExists, "pathExists");
|
|
15472
15560
|
function pathIsSymlink(path) {
|
|
15473
15561
|
try {
|
|
15474
|
-
return
|
|
15562
|
+
return lstatSync3(path).isSymbolicLink();
|
|
15475
15563
|
} catch {
|
|
15476
15564
|
return false;
|
|
15477
15565
|
}
|
|
@@ -15837,7 +15925,7 @@ function buildRecoveryIndex(repoRoot, realIndex, recoveryIndex, newHead, committ
|
|
|
15837
15925
|
__name(buildRecoveryIndex, "buildRecoveryIndex");
|
|
15838
15926
|
function installRecoveryIndex(realIndex, recoveryIndex, expectedHash) {
|
|
15839
15927
|
try {
|
|
15840
|
-
if (!
|
|
15928
|
+
if (!lstatSync3(recoveryIndex).isFile() || lstatSync3(recoveryIndex).isSymbolicLink()) return false;
|
|
15841
15929
|
} catch {
|
|
15842
15930
|
return false;
|
|
15843
15931
|
}
|
|
@@ -15900,7 +15988,7 @@ function recoveryPreflight(repoRoot) {
|
|
|
15900
15988
|
const stateDir = join7(gitDir(repoRoot), "oms");
|
|
15901
15989
|
let stateExists = false;
|
|
15902
15990
|
try {
|
|
15903
|
-
const stat =
|
|
15991
|
+
const stat = lstatSync3(stateDir);
|
|
15904
15992
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
15905
15993
|
return { ok: false, reason: `unsafe OMS state directory: ${stateDir}` };
|
|
15906
15994
|
}
|
|
@@ -16723,12 +16811,7 @@ async function runSync(aliases, options) {
|
|
|
16723
16811
|
printList(repos);
|
|
16724
16812
|
return 0;
|
|
16725
16813
|
}
|
|
16726
|
-
if (
|
|
16727
|
-
R2.error(
|
|
16728
|
-
`${repoRoot} is not a git repository. oh-my-space 0.6.0 manages sources as git submodules; run "git init" at the workspace root first.`
|
|
16729
|
-
);
|
|
16730
|
-
return 1;
|
|
16731
|
-
}
|
|
16814
|
+
if (emitWorkspaceGitIdentityError(repoRoot)) return 1;
|
|
16732
16815
|
if (abortOnLegacyWorktree(repoRoot, repos)) return 1;
|
|
16733
16816
|
const picked = await selectRepos(repos, aliases, options, "sync");
|
|
16734
16817
|
if (!picked || picked.length === 0) return 1;
|
|
@@ -17613,7 +17696,7 @@ async function runDoctor() {
|
|
|
17613
17696
|
}
|
|
17614
17697
|
const { repos, repoRoot } = loaded;
|
|
17615
17698
|
if (abortOnLegacyRenameAt(repoRoot)) return 1;
|
|
17616
|
-
R2.success(`Workspace
|
|
17699
|
+
R2.success(`Workspace manifest directory: ${repoRoot}`);
|
|
17617
17700
|
R2.success(`${MANIFEST_FILENAME}: ${repos.length} repo(s) configured`);
|
|
17618
17701
|
const git = spawnSync3("git", ["--version"], {
|
|
17619
17702
|
encoding: "utf8",
|
|
@@ -17637,12 +17720,26 @@ async function runDoctor() {
|
|
|
17637
17720
|
);
|
|
17638
17721
|
warnings++;
|
|
17639
17722
|
}
|
|
17640
|
-
|
|
17723
|
+
const identity = inspectWorkspaceGitIdentity(repoRoot);
|
|
17724
|
+
if (identity.kind === "no-work-tree") {
|
|
17641
17725
|
R2.warn(
|
|
17642
17726
|
`workspace is not a git repository. oms manages sources as submodules; run "git init" at ${repoRoot}.`
|
|
17643
17727
|
);
|
|
17644
|
-
|
|
17728
|
+
return 2;
|
|
17645
17729
|
}
|
|
17730
|
+
if (identity.kind === "mismatch") {
|
|
17731
|
+
R2.error(
|
|
17732
|
+
`Workspace manifest directory ${repoRoot} does not match the root Git top-level ${identity.gitTopLevel}. Move ${MANIFEST_FILENAME} to ${identity.gitTopLevel}, or initialize a separate Git repository at ${repoRoot}.`
|
|
17733
|
+
);
|
|
17734
|
+
return 1;
|
|
17735
|
+
}
|
|
17736
|
+
if (identity.kind === "indeterminate") {
|
|
17737
|
+
R2.error(
|
|
17738
|
+
`Could not verify that workspace ${repoRoot} is the root Git top-level: ${identity.reason}. Retry after the workspace path and Git repository are accessible.`
|
|
17739
|
+
);
|
|
17740
|
+
return 1;
|
|
17741
|
+
}
|
|
17742
|
+
R2.success(`Workspace root: ${repoRoot}`);
|
|
17646
17743
|
if (abortOnLegacyWorktree(repoRoot, repos)) return 1;
|
|
17647
17744
|
if (gitignoreIgnoresOms(repoRoot)) {
|
|
17648
17745
|
R2.warn(`.gitignore excludes ${GITIGNORE_ENTRY}, but submodules must be tracked. Run "oms sync" to remove it.`);
|
|
@@ -17676,6 +17773,16 @@ __name(runDoctor, "runDoctor");
|
|
|
17676
17773
|
|
|
17677
17774
|
// scripts/lib/help.ts
|
|
17678
17775
|
var exitHelp = "\nExit codes: 0 ok | 1 usage/config error | 2 one or more git operations failed.";
|
|
17776
|
+
var workspaceContextHelp = `
|
|
17777
|
+
Workspace discovery uses the nearest oms.yaml from the current directory upward. An invalid nearest
|
|
17778
|
+
candidate is an error; OMS never falls back to a more distant manifest. Commands that inspect or change
|
|
17779
|
+
submodules require that manifest directory to be the root Git top-level. Move a nested manifest to the
|
|
17780
|
+
Git root, or initialize a separate Git repository at the intended workspace root.
|
|
17781
|
+
`;
|
|
17782
|
+
var initHelp = `
|
|
17783
|
+
The current directory must be outside a Git work tree or be its root top-level. Init refuses a child of
|
|
17784
|
+
an existing Git work tree before changing any file, including with --force.
|
|
17785
|
+
`;
|
|
17679
17786
|
var statusHelp = `
|
|
17680
17787
|
Machine-readable mode prints exactly one JSON object on stdout. The schemaVersion 1 payload has seven
|
|
17681
17788
|
top-level keys: schemaVersion, toolVersion, workspaceRoot, currentAlias, root, repos, and errors.
|
|
@@ -17690,6 +17797,7 @@ Examples:
|
|
|
17690
17797
|
var commitHelp = `
|
|
17691
17798
|
Scope: commits inside the selected oms/<alias>/ submodule only \u2014 never the root gitlink. Existing staged
|
|
17692
17799
|
changes are committed as-is (staged-first); otherwise all changes are staged with git add -A.
|
|
17800
|
+
An explicit alias takes precedence over an alias inferred from the current configured oms/<alias>/ path.
|
|
17693
17801
|
Examples:
|
|
17694
17802
|
$ oms commit api -m "feat: add login" # commit submodule source changes
|
|
17695
17803
|
$ oms commit -m "fix: typo" # infer the alias from the current oms/<alias>/ directory
|
|
@@ -17697,6 +17805,7 @@ Examples:
|
|
|
17697
17805
|
var recordHelp = `
|
|
17698
17806
|
Scope: commits an existing root gitlink pointer update for one alias in the ROOT repository only
|
|
17699
17807
|
(chore(oms): update <alias> submodule to <sha>). It never adds or removes a submodule registration.
|
|
17808
|
+
An explicit alias takes precedence over an alias inferred from the current configured oms/<alias>/ path.
|
|
17700
17809
|
Example:
|
|
17701
17810
|
$ oms record api
|
|
17702
17811
|
`;
|
|
@@ -17764,15 +17873,29 @@ repos:
|
|
|
17764
17873
|
branch: main
|
|
17765
17874
|
`;
|
|
17766
17875
|
async function runInit(options) {
|
|
17767
|
-
const
|
|
17876
|
+
const cwd = process.cwd();
|
|
17877
|
+
const target = join11(cwd, MANIFEST_FILENAME);
|
|
17878
|
+
const identity = inspectWorkspaceGitIdentity(cwd);
|
|
17879
|
+
if (identity.kind === "mismatch") {
|
|
17880
|
+
R2.error(
|
|
17881
|
+
`Cannot initialize a workspace at ${cwd} because it is below the root Git top-level ${identity.gitTopLevel}. Run "oms init" at ${identity.gitTopLevel}, or initialize a separate Git repository at ${cwd}.`
|
|
17882
|
+
);
|
|
17883
|
+
return 1;
|
|
17884
|
+
}
|
|
17885
|
+
if (identity.kind === "indeterminate") {
|
|
17886
|
+
R2.error(
|
|
17887
|
+
`Could not verify the workspace target ${cwd}: ${identity.reason}. No files were changed; retry after the path and Git repository are accessible.`
|
|
17888
|
+
);
|
|
17889
|
+
return 1;
|
|
17890
|
+
}
|
|
17768
17891
|
if (existsSync11(target) && !options.force) {
|
|
17769
17892
|
R2.error(`${MANIFEST_FILENAME} already exists at ${target}. Use --force to overwrite.`);
|
|
17770
17893
|
return 1;
|
|
17771
17894
|
}
|
|
17772
17895
|
writeFileSync6(target, INIT_TEMPLATE);
|
|
17773
17896
|
R2.success(`created ${MANIFEST_FILENAME} at ${target}`);
|
|
17774
|
-
ensureOmsNotIgnored(
|
|
17775
|
-
if (
|
|
17897
|
+
ensureOmsNotIgnored(cwd);
|
|
17898
|
+
if (identity.kind === "no-work-tree") {
|
|
17776
17899
|
R2.info(`oms manages sources as git submodules; run "git init" here if this is not a git repo yet.`);
|
|
17777
17900
|
}
|
|
17778
17901
|
R2.info(`edit alias/remotes/branch, then run "oms sync".`);
|
|
@@ -17936,7 +18059,7 @@ import { spawnSync as spawnSync5 } from "node:child_process";
|
|
|
17936
18059
|
var import_semver = __toESM(require_semver2(), 1);
|
|
17937
18060
|
|
|
17938
18061
|
// scripts/lib/install-context.ts
|
|
17939
|
-
import { existsSync as existsSync12, realpathSync } from "node:fs";
|
|
18062
|
+
import { existsSync as existsSync12, realpathSync as realpathSync2 } from "node:fs";
|
|
17940
18063
|
import { delimiter, dirname as dirname5, join as join12, resolve as resolve5 } from "node:path";
|
|
17941
18064
|
function formatCommand(command) {
|
|
17942
18065
|
return [command.executable, ...command.args].join(" ");
|
|
@@ -17951,7 +18074,7 @@ function globalUpdateCommand(manager) {
|
|
|
17951
18074
|
__name(globalUpdateCommand, "globalUpdateCommand");
|
|
17952
18075
|
function safeRealpath(path) {
|
|
17953
18076
|
try {
|
|
17954
|
-
return
|
|
18077
|
+
return realpathSync2(path);
|
|
17955
18078
|
} catch {
|
|
17956
18079
|
return path;
|
|
17957
18080
|
}
|
|
@@ -18456,40 +18579,40 @@ var collectRepeatable = /* @__PURE__ */ __name((value, acc) => [...acc, value],
|
|
|
18456
18579
|
var program2 = new Command();
|
|
18457
18580
|
program2.name("oms").description(
|
|
18458
18581
|
`Manage source repositories listed in ${MANIFEST_FILENAME} as git submodules under ${DATA_DIRNAME}/<alias>/.`
|
|
18459
|
-
).version(readPackageVersion()).addHelpText("after", exitHelp);
|
|
18460
|
-
program2.command("init").description(`Create a starter ${MANIFEST_FILENAME} in the current directory.`).option("--force", `overwrite an existing ${MANIFEST_FILENAME}`).addHelpText("after", exitHelp).action(async (options) => {
|
|
18582
|
+
).version(readPackageVersion()).addHelpText("after", `${workspaceContextHelp}${exitHelp}`);
|
|
18583
|
+
program2.command("init").description(`Create a starter ${MANIFEST_FILENAME} in the current directory.`).option("--force", `overwrite an existing ${MANIFEST_FILENAME}`).addHelpText("after", `${initHelp}${exitHelp}`).action(async (options) => {
|
|
18461
18584
|
await exitWith(runInit(options));
|
|
18462
18585
|
});
|
|
18463
18586
|
program2.command("doctor").description(
|
|
18464
18587
|
`Check ${MANIFEST_FILENAME}, git availability, and the submodule state of each registered alias.`
|
|
18465
|
-
).addHelpText("after", exitHelp).action(async () => {
|
|
18588
|
+
).addHelpText("after", `${workspaceContextHelp}${exitHelp}`).action(async () => {
|
|
18466
18589
|
await exitWith(runDoctor());
|
|
18467
18590
|
});
|
|
18468
18591
|
program2.command("sync").description(
|
|
18469
18592
|
`Register each repo as a submodule at ${DATA_DIRNAME}/<alias>/ (or initialize and refresh an existing one), checked out on its baseline branch.`
|
|
18470
|
-
).argument("[aliases...]", "repo aliases to sync (omit for interactive multi-select)").option("--all", "sync every registered source repo").option("--list", "print registered repos").option("--commit", "create the root topology commit (chore(oms): add ...) without prompting").addHelpText("after", `${syncHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18593
|
+
).argument("[aliases...]", "repo aliases to sync (omit for interactive multi-select)").option("--all", "sync every registered source repo").option("--list", "print registered repos").option("--commit", "create the root topology commit (chore(oms): add ...) without prompting").addHelpText("after", `${syncHelp}${workspaceContextHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18471
18594
|
await exitWith(runSync(aliases, options));
|
|
18472
18595
|
});
|
|
18473
|
-
program2.command("status").description("Show each submodule's branch, pointer state, dirtiness, and ahead/behind counts.").argument("[aliases...]", "repo aliases to inspect (omit for all)").option("--all", "inspect every registered source repo").option("--json", "print machine-readable workspace state (one JSON object on stdout)").addHelpText("after", `${statusHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18596
|
+
program2.command("status").description("Show each submodule's branch, pointer state, dirtiness, and ahead/behind counts.").argument("[aliases...]", "repo aliases to inspect (omit for all)").option("--all", "inspect every registered source repo").option("--json", "print machine-readable workspace state (one JSON object on stdout)").addHelpText("after", `${statusHelp}${workspaceContextHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18474
18597
|
await exitWith(runStatus(aliases, options));
|
|
18475
18598
|
});
|
|
18476
|
-
program2.command("commit").description("Commit source changes inside the selected submodule only (never the root gitlink).").argument("[alias]", "registered source alias (omit to infer from the current oms/<alias>/ directory)").option("-m, --message <message>", "commit message (repeatable; required only to create a commit)", collectRepeatable, []).addHelpText("after", `${commitHelp}${exitHelp}`).action(async (alias, options) => {
|
|
18599
|
+
program2.command("commit").description("Commit source changes inside the selected submodule only (never the root gitlink).").argument("[alias]", "registered source alias (omit to infer from the current oms/<alias>/ directory)").option("-m, --message <message>", "commit message (repeatable; required only to create a commit)", collectRepeatable, []).addHelpText("after", `${commitHelp}${workspaceContextHelp}${exitHelp}`).action(async (alias, options) => {
|
|
18477
18600
|
await exitWith(runCommit(alias, options));
|
|
18478
18601
|
});
|
|
18479
|
-
program2.command("record").description("Commit an existing root gitlink pointer update for the selected submodule (root repo only).").argument("[alias]", "registered source alias (omit to infer from the current oms/<alias>/ directory)").addHelpText("after", `${recordHelp}${exitHelp}`).action(async (alias) => {
|
|
18602
|
+
program2.command("record").description("Commit an existing root gitlink pointer update for the selected submodule (root repo only).").argument("[alias]", "registered source alias (omit to infer from the current oms/<alias>/ directory)").addHelpText("after", `${recordHelp}${workspaceContextHelp}${exitHelp}`).action(async (alias) => {
|
|
18480
18603
|
await exitWith(runRecord(alias));
|
|
18481
18604
|
});
|
|
18482
|
-
var branchCommand = program2.command("branch").description("Inspect, switch, checkout, or delete submodule branches (interactive action selector).").addHelpText("after", exitHelp).action(async () => {
|
|
18605
|
+
var branchCommand = program2.command("branch").description("Inspect, switch, checkout, or delete submodule branches (interactive action selector).").addHelpText("after", `${workspaceContextHelp}${exitHelp}`).action(async () => {
|
|
18483
18606
|
await exitWith(runBranch(branchCommand));
|
|
18484
18607
|
});
|
|
18485
18608
|
branchCommand.command("switch").description(
|
|
18486
18609
|
"Switch a submodule to a LOCAL branch, creating it locally if it does not exist yet (no remote required)."
|
|
18487
|
-
).argument("[alias]", "registered source alias (omit to pick interactively)").argument("[branch]", "local branch name (omit to pick from local branches or create one)").option("--from <ref>", "start point for a new branch (default: current HEAD)").addHelpText("after", exitHelp).action(async (alias, branch, options) => {
|
|
18610
|
+
).argument("[alias]", "registered source alias (omit to pick interactively)").argument("[branch]", "local branch name (omit to pick from local branches or create one)").option("--from <ref>", "start point for a new branch (default: current HEAD)").addHelpText("after", `${workspaceContextHelp}${exitHelp}`).action(async (alias, branch, options) => {
|
|
18488
18611
|
await exitWith(runSwitch(alias, branch, options));
|
|
18489
18612
|
});
|
|
18490
18613
|
branchCommand.command("checkout").description(
|
|
18491
18614
|
"Fetch origin, then check out a REMOTE branch (origin/*) as a local tracking branch."
|
|
18492
|
-
).argument("[alias]", "registered source alias (omit to pick interactively)").argument("[branch]", "remote branch name (omit to pick from origin/* branches)").addHelpText("after", exitHelp).action(async (alias, branch) => {
|
|
18615
|
+
).argument("[alias]", "registered source alias (omit to pick interactively)").argument("[branch]", "remote branch name (omit to pick from origin/* branches)").addHelpText("after", `${workspaceContextHelp}${exitHelp}`).action(async (alias, branch) => {
|
|
18493
18616
|
await exitWith(runCheckout(alias, branch));
|
|
18494
18617
|
});
|
|
18495
18618
|
branchCommand.command("list").description("Prepare one submodule, refresh declared remotes, and list local and remote branches.").argument("[alias]", "declared source alias (the sole alias is selected automatically)").addHelpText(
|
|
@@ -18504,31 +18627,31 @@ Behavior:
|
|
|
18504
18627
|
Examples:
|
|
18505
18628
|
$ oms branch list api
|
|
18506
18629
|
$ oms branch list
|
|
18507
|
-
${exitHelp}`
|
|
18630
|
+
${workspaceContextHelp}${exitHelp}`
|
|
18508
18631
|
).action(async (alias) => {
|
|
18509
18632
|
await exitWith(runBranchList(alias));
|
|
18510
18633
|
});
|
|
18511
18634
|
branchCommand.command("delete").description(
|
|
18512
18635
|
"Delete a LOCAL branch inside one initialized submodule (never a remote branch or the root gitlink)."
|
|
18513
|
-
).argument("[alias]", "registered source alias (omit to pick interactively)").argument("[branch]", "local branch name (omit to pick from deletable local branches)").option("-f, --force", "force-delete with git branch -D (still respects protected branches)").addHelpText("after", exitHelp).action(async (alias, branch, options) => {
|
|
18636
|
+
).argument("[alias]", "registered source alias (omit to pick interactively)").argument("[branch]", "local branch name (omit to pick from deletable local branches)").option("-f, --force", "force-delete with git branch -D (still respects protected branches)").addHelpText("after", `${workspaceContextHelp}${exitHelp}`).action(async (alias, branch, options) => {
|
|
18514
18637
|
await exitWith(runBranchDelete(alias, branch, options));
|
|
18515
18638
|
});
|
|
18516
|
-
program2.command("fetch").description("Run git fetch <remote> --prune in each submodule (defaults to origin).").argument("[aliases...]", "repo aliases to fetch (omit for interactive multi-select)").option("--all", "fetch every registered source repo").option("--remote <name>", "remote to fetch (repeatable; omit to choose interactively)", collectRepeatable, []).addHelpText("after", exitHelp).action(async (aliases, options) => {
|
|
18639
|
+
program2.command("fetch").description("Run git fetch <remote> --prune in each submodule (defaults to origin).").argument("[aliases...]", "repo aliases to fetch (omit for interactive multi-select)").option("--all", "fetch every registered source repo").option("--remote <name>", "remote to fetch (repeatable; omit to choose interactively)", collectRepeatable, []).addHelpText("after", `${workspaceContextHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18517
18640
|
await exitWith(runManage("fetch", aliases, options));
|
|
18518
18641
|
});
|
|
18519
18642
|
program2.command("pull").description(
|
|
18520
18643
|
"Pull the submodule branch only (git pull --ff-only <remote>); never stages or commits the root gitlink (defaults to origin)."
|
|
18521
|
-
).argument("[aliases...]", "repo aliases to pull (omit for interactive multi-select)").option("--all", "pull every registered source repo").option("--remote <name>", "remote to pull from (single; omit to choose interactively)", collectRepeatable, []).addHelpText("after", `${pullHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18644
|
+
).argument("[aliases...]", "repo aliases to pull (omit for interactive multi-select)").option("--all", "pull every registered source repo").option("--remote <name>", "remote to pull from (single; omit to choose interactively)", collectRepeatable, []).addHelpText("after", `${pullHelp}${workspaceContextHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18522
18645
|
await exitWith(runManage("pull", aliases, options));
|
|
18523
18646
|
});
|
|
18524
18647
|
program2.command("push").description(
|
|
18525
18648
|
'Push the submodule branch only (creating the remote branch on first push); never stages or commits the root gitlink. Use "oms record <alias>" for root pointer commits (defaults to origin).'
|
|
18526
|
-
).argument("<aliases...>", "repo aliases to push").option("--commit", 'unsupported: use "oms record <alias>" after pushing').option("--record", 'unsupported: use "oms record <alias>" after pushing').option("--remote <name>", "remote to push to (repeatable; omit to choose interactively)", collectRepeatable, []).addHelpText("after", `${pushHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18649
|
+
).argument("<aliases...>", "repo aliases to push").option("--commit", 'unsupported: use "oms record <alias>" after pushing').option("--record", 'unsupported: use "oms record <alias>" after pushing').option("--remote <name>", "remote to push to (repeatable; omit to choose interactively)", collectRepeatable, []).addHelpText("after", `${pushHelp}${workspaceContextHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18527
18650
|
await exitWith(runManage("push", aliases, options));
|
|
18528
18651
|
});
|
|
18529
18652
|
program2.command("unsync").description(
|
|
18530
18653
|
`Deinitialize and remove the submodule for each alias (keeps ${MANIFEST_FILENAME} entry).`
|
|
18531
|
-
).argument("[aliases...]", "repo aliases to unsync (omit for interactive multi-select)").option("--all", "unsync every registered source repo").option("--force", "discard uncommitted changes in the submodule").option("--commit", "create the root topology commit (chore(oms): remove ...) without prompting").addHelpText("after", `${unsyncHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18654
|
+
).argument("[aliases...]", "repo aliases to unsync (omit for interactive multi-select)").option("--all", "unsync every registered source repo").option("--force", "discard uncommitted changes in the submodule").option("--commit", "create the root topology commit (chore(oms): remove ...) without prompting").addHelpText("after", `${unsyncHelp}${workspaceContextHelp}${exitHelp}`).action(async (aliases, options) => {
|
|
18532
18655
|
await exitWith(runUnsync(aliases, options));
|
|
18533
18656
|
});
|
|
18534
18657
|
var agentCommand = program2.command("agent").description(`Manage OMS agent instruction blocks under ${DATA_DIRNAME}/ (AGENTS.md, CLAUDE.md).`).addHelpText("after", exitHelp);
|