oh-my-space 0.11.3 → 0.11.4

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.
@@ -1 +1 @@
1
- {"commit":"1c33003a35ff5de34f10fe4341a38338fa927f95"}
1
+ {"commit":"60bbfdfcc5f72dce2b2b74508715ddbc723aa3ff"}
package/dist/oms.js CHANGED
@@ -14014,6 +14014,8 @@ function readJson(path) {
14014
14014
  }
14015
14015
  __name(readJson, "readJson");
14016
14016
  function readPackageVersion() {
14017
+ const mocked = testEnv("OMS_TEST_PACKAGE_VERSION");
14018
+ if (mocked !== void 0) return mocked;
14017
14019
  const pkg = readJson(join(packageRoot, "package.json"));
14018
14020
  return pkg?.version ?? "0.0.0";
14019
14021
  }
@@ -15764,7 +15766,7 @@ async function runInit(options) {
15764
15766
  __name(runInit, "runInit");
15765
15767
 
15766
15768
  // scripts/lib/repo-ops.ts
15767
- import { existsSync as existsSync8, readdirSync, rmSync as rmSync2, rmdirSync, writeFileSync as writeFileSync4 } from "node:fs";
15769
+ import { existsSync as existsSync8, lstatSync, readFileSync as readFileSync4, readdirSync, rmSync as rmSync2, rmdirSync, writeFileSync as writeFileSync4 } from "node:fs";
15768
15770
  import { join as join7 } from "node:path";
15769
15771
  function cleanupFailedAdd(repoRoot, alias) {
15770
15772
  const path = submodulePath(alias);
@@ -15782,12 +15784,167 @@ function cleanupFailedAdd(repoRoot, alias) {
15782
15784
  }
15783
15785
  }
15784
15786
  __name(cleanupFailedAdd, "cleanupFailedAdd");
15787
+ function headGitmodulesSection(repoRoot, alias) {
15788
+ const path = submodulePath(alias);
15789
+ const r = runGit(repoRoot, ["show", "HEAD:.gitmodules"]);
15790
+ if (!r.success) return null;
15791
+ const lines = r.stdout.split("\n");
15792
+ const start = lines.findIndex((line) => line.trim() === `[submodule "${path}"]`);
15793
+ if (start === -1) return null;
15794
+ let end = lines.length;
15795
+ for (let i = start + 1; i < lines.length; i++) {
15796
+ if (/^\s*\[submodule ".+"\]\s*$/.test(lines[i])) {
15797
+ end = i;
15798
+ break;
15799
+ }
15800
+ }
15801
+ return `${lines.slice(start, end).join("\n").replace(/\n*$/, "\n")}`;
15802
+ }
15803
+ __name(headGitmodulesSection, "headGitmodulesSection");
15804
+ function headFollowingGitmodulesHeaders(repoRoot, alias) {
15805
+ const path = submodulePath(alias);
15806
+ const r = runGit(repoRoot, ["show", "HEAD:.gitmodules"]);
15807
+ if (!r.success) return [];
15808
+ const lines = r.stdout.split("\n");
15809
+ const start = lines.findIndex((line) => line.trim() === `[submodule "${path}"]`);
15810
+ if (start === -1) return [];
15811
+ const headers = [];
15812
+ for (let i = start + 1; i < lines.length; i++) {
15813
+ const match = lines[i].trim().match(/^\[submodule "(.+)"\]$/);
15814
+ if (match) headers.push(`[submodule "${match[1]}"]`);
15815
+ }
15816
+ return headers;
15817
+ }
15818
+ __name(headFollowingGitmodulesHeaders, "headFollowingGitmodulesHeaders");
15819
+ function currentGitmodulesHasSameAliasSection(repoRoot, alias) {
15820
+ const gitmodules = join7(repoRoot, ".gitmodules");
15821
+ if (!existsSync8(gitmodules)) return false;
15822
+ return readFileSync4(gitmodules, "utf8").split("\n").some((line) => line.trim() === `[submodule "${submodulePath(alias)}"]`);
15823
+ }
15824
+ __name(currentGitmodulesHasSameAliasSection, "currentGitmodulesHasSameAliasSection");
15825
+ function insertGitmodulesSection(repoRoot, alias, section) {
15826
+ const gitmodules = join7(repoRoot, ".gitmodules");
15827
+ const existing = existsSync8(gitmodules) ? readFileSync4(gitmodules, "utf8") : "";
15828
+ const followingHeaders = headFollowingGitmodulesHeaders(repoRoot, alias);
15829
+ const lines = existing.split("\n");
15830
+ for (const followingHeader of followingHeaders) {
15831
+ const insertAt = lines.findIndex((line) => line.trim() === followingHeader);
15832
+ if (insertAt !== -1) {
15833
+ lines.splice(insertAt, 0, ...section.replace(/\n$/, "").split("\n"));
15834
+ writeFileSync4(gitmodules, lines.join("\n"));
15835
+ return;
15836
+ }
15837
+ }
15838
+ const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
15839
+ writeFileSync4(gitmodules, `${existing}${separator}${section}`);
15840
+ }
15841
+ __name(insertGitmodulesSection, "insertGitmodulesSection");
15842
+ function gitmoduleValue(repoRoot, alias, key) {
15843
+ const r = runGit(repoRoot, ["config", "--file", ".gitmodules", "--get", `submodule.${submodulePath(alias)}.${key}`]);
15844
+ const value = r.stdout.trim();
15845
+ return r.success && value.length > 0 ? value : null;
15846
+ }
15847
+ __name(gitmoduleValue, "gitmoduleValue");
15848
+ function reconcileGitmodulesMetadata(repoRoot, repo) {
15849
+ const path = submodulePath(repo.alias);
15850
+ let changed = false;
15851
+ if (gitmoduleValue(repoRoot, repo.alias, "url") !== repo.remotes.origin) {
15852
+ runGit(repoRoot, ["config", "--file", ".gitmodules", `submodule.${path}.url`, repo.remotes.origin]);
15853
+ changed = true;
15854
+ }
15855
+ const currentBranchValue = gitmoduleValue(repoRoot, repo.alias, "branch");
15856
+ if (repo.branch) {
15857
+ if (currentBranchValue !== repo.branch) {
15858
+ runGit(repoRoot, ["config", "--file", ".gitmodules", `submodule.${path}.branch`, repo.branch]);
15859
+ changed = true;
15860
+ }
15861
+ } else if (currentBranchValue !== null) {
15862
+ runGit(repoRoot, ["config", "--file", ".gitmodules", "--unset", `submodule.${path}.branch`]);
15863
+ changed = true;
15864
+ }
15865
+ return changed;
15866
+ }
15867
+ __name(reconcileGitmodulesMetadata, "reconcileGitmodulesMetadata");
15868
+ function readAliasDirEntries(repoRoot, alias) {
15869
+ const dir = aliasDir(repoRoot, alias);
15870
+ try {
15871
+ if (!lstatSync(dir).isDirectory()) return { exists: true, entries: null };
15872
+ return { exists: true, entries: readdirSync(dir) };
15873
+ } catch (error) {
15874
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
15875
+ return { exists: false };
15876
+ }
15877
+ return { exists: true, entries: null };
15878
+ }
15879
+ }
15880
+ __name(readAliasDirEntries, "readAliasDirEntries");
15881
+ function cleanupRestorableAliasDir(repoRoot, alias) {
15882
+ if (submoduleInitialized(repoRoot, alias)) return true;
15883
+ const dirState = readAliasDirEntries(repoRoot, alias);
15884
+ if (!dirState.exists) return true;
15885
+ if (dirState.entries === null) return false;
15886
+ const entries = dirState.entries;
15887
+ if (entries.length > 0 && entries.some((entry) => entry !== ".DS_Store")) return false;
15888
+ try {
15889
+ rmSync2(aliasDir(repoRoot, alias), { recursive: true, force: true });
15890
+ } catch {
15891
+ return false;
15892
+ }
15893
+ return true;
15894
+ }
15895
+ __name(cleanupRestorableAliasDir, "cleanupRestorableAliasDir");
15896
+ function restorePendingRemoval(repo, repoRoot) {
15897
+ const alias = repo.alias;
15898
+ const path = submodulePath(alias);
15899
+ const state = gitlinkState(repoRoot, alias);
15900
+ const shouldRestore = state.headOid !== null && (pendingRemovalTopology(state) || partialRemovalTopology(state));
15901
+ if (!shouldRestore) return { result: "failed", restored: false };
15902
+ const unsafe = /* @__PURE__ */ __name((detail) => {
15903
+ R2.error(`${alias}: cannot restore pending removal safely (${detail}). Resolve or commit the pending removal before syncing.`);
15904
+ return { result: "failed", restored: true };
15905
+ }, "unsafe");
15906
+ if (state.conflict) return unsafe("root gitlink is conflicted");
15907
+ const rootOp = gitOperationInProgress(repoRoot);
15908
+ if (rootOp) return unsafe(`root repository has a ${rootOp} in progress`);
15909
+ if (currentGitmodulesHasSameAliasSection(repoRoot, alias) && !state.gitmodulesEntry) {
15910
+ return unsafe("current .gitmodules has an incomplete same-alias section");
15911
+ }
15912
+ const section = headGitmodulesSection(repoRoot, alias);
15913
+ if (!section) return unsafe(".gitmodules metadata is not recoverable from HEAD");
15914
+ if (!cleanupRestorableAliasDir(repoRoot, alias)) {
15915
+ return unsafe(`${path} is occupied by a non-submodule path`);
15916
+ }
15917
+ runGit(repoRoot, ["restore", "--source=HEAD", "--staged", "--", ".gitmodules"]);
15918
+ if (!state.gitmodulesEntry) insertGitmodulesSection(repoRoot, alias, section);
15919
+ const metadataUpdated = reconcileGitmodulesMetadata(repoRoot, repo);
15920
+ const restorePath = runGit(repoRoot, ["restore", "--source=HEAD", "--staged", "--worktree", "--", path], true);
15921
+ if (!restorePath.success) return unsafe(`failed to restore ${path} from HEAD`);
15922
+ runGit(repoRoot, ["submodule", "sync", "--", path]);
15923
+ R2.step(`${alias}: git submodule update --init ${path}`);
15924
+ const upd = runGit(repoRoot, ["submodule", "update", "--init", "--", path], true);
15925
+ if (!upd.success) {
15926
+ R2.error(`${alias}: git submodule update --init failed (exit ${upd.exitCode})`);
15927
+ return { result: "failed", restored: true };
15928
+ }
15929
+ ensureRemotes(repoRoot, alias, repo.remotes);
15930
+ const branch = gitmodulesBranch(repoRoot, alias) ?? repo.branch;
15931
+ if (branch) attachBranch(repoRoot, alias, branch);
15932
+ R2.success(`${alias}: restored pending removal${metadataUpdated ? " and updated .gitmodules metadata" : ""}`);
15933
+ return { result: "added", restored: true };
15934
+ }
15935
+ __name(restorePendingRemoval, "restorePendingRemoval");
15785
15936
  function syncRepo(repo, repoRoot) {
15786
15937
  const alias = repo.alias;
15787
15938
  const path = submodulePath(alias);
15788
15939
  const registered = isRegisteredSubmodule(repoRoot, path);
15940
+ const state = gitlinkState(repoRoot, alias);
15941
+ if (!registered && state.headOid !== null) {
15942
+ const restore = restorePendingRemoval(repo, repoRoot);
15943
+ if (restore.restored) return restore.result;
15944
+ }
15789
15945
  if (!registered) {
15790
- if (existsSync8(aliasDir(repoRoot, alias)) && readdirSync(aliasDir(repoRoot, alias)).length > 0) {
15946
+ const dirState = readAliasDirEntries(repoRoot, alias);
15947
+ if (dirState.exists && (dirState.entries === null || dirState.entries.length > 0)) {
15791
15948
  R2.error(
15792
15949
  `${alias}: ${path}/ already exists but is not a registered submodule. Move or remove it manually, then retry.`
15793
15950
  );
@@ -16249,6 +16406,12 @@ function compareVersions(currentVersion, latestVersion) {
16249
16406
  return import_semver.default.compare(current, latest);
16250
16407
  }
16251
16408
  __name(compareVersions, "compareVersions");
16409
+ function isPrereleaseVersion(version) {
16410
+ const parsed = import_semver.default.valid(version);
16411
+ if (!parsed) throw new Error(`Installed version is not valid semver: ${version}`);
16412
+ return import_semver.default.prerelease(parsed) !== null;
16413
+ }
16414
+ __name(isPrereleaseVersion, "isPrereleaseVersion");
16252
16415
  function printUpdateHeader(currentVersion, latestVersion) {
16253
16416
  R2.info(`Current version: ${currentVersion}`);
16254
16417
  R2.info(`Latest version: ${latestVersion}`);
@@ -16266,6 +16429,36 @@ function printGuidance(context) {
16266
16429
  for (const command of context.guidance) R2.message(` ${command}`);
16267
16430
  }
16268
16431
  __name(printGuidance, "printGuidance");
16432
+ function channelInstallCommand(manager, tag) {
16433
+ if (manager === "npm") return `npm install -g ${PACKAGE_NAME}@${tag}`;
16434
+ if (manager === "pnpm") return `pnpm add -g ${PACKAGE_NAME}@${tag}`;
16435
+ if (manager === "yarn") return `yarn global add ${PACKAGE_NAME}@${tag}`;
16436
+ return `bun add -g ${PACKAGE_NAME}@${tag}`;
16437
+ }
16438
+ __name(channelInstallCommand, "channelInstallCommand");
16439
+ function prereleaseGuidanceManager(context) {
16440
+ return context.manager ?? context.updateCommand?.executable ?? null;
16441
+ }
16442
+ __name(prereleaseGuidanceManager, "prereleaseGuidanceManager");
16443
+ function printPrereleaseGuidance(context) {
16444
+ const manager = prereleaseGuidanceManager(context);
16445
+ R2.info("Prerelease channel guidance:");
16446
+ if (manager) {
16447
+ R2.message(` Stay on beta manually: ${channelInstallCommand(manager, "beta")}`);
16448
+ R2.message(` Return to stable: ${channelInstallCommand(manager, "latest")}`);
16449
+ return;
16450
+ }
16451
+ for (const fallbackManager of ["npm", "pnpm", "yarn", "bun"]) {
16452
+ R2.message(` ${fallbackManager} beta: ${channelInstallCommand(fallbackManager, "beta")}`);
16453
+ R2.message(` ${fallbackManager} stable: ${channelInstallCommand(fallbackManager, "latest")}`);
16454
+ }
16455
+ }
16456
+ __name(printPrereleaseGuidance, "printPrereleaseGuidance");
16457
+ function printPrereleaseStatus(currentVersion, latestVersion) {
16458
+ R2.info(`Installed prerelease version: ${currentVersion}`);
16459
+ R2.info(`Stable latest version: ${latestVersion}`);
16460
+ }
16461
+ __name(printPrereleaseStatus, "printPrereleaseStatus");
16269
16462
  function commandAvailability(command) {
16270
16463
  const mocked = testEnv("OMS_TEST_MANAGER_AVAILABLE");
16271
16464
  if (mocked !== void 0) return mocked === "1" || mocked === "true";
@@ -16324,12 +16517,16 @@ async function runUpdate(options) {
16324
16517
  try {
16325
16518
  latestVersion = await fetchLatestPackageVersion();
16326
16519
  const comparison = compareVersions(currentVersion, latestVersion);
16520
+ const prerelease2 = isPrereleaseVersion(currentVersion);
16327
16521
  printUpdateHeader(currentVersion, latestVersion);
16522
+ if (prerelease2) printPrereleaseStatus(currentVersion, latestVersion);
16328
16523
  if (comparison === 0) {
16524
+ if (prerelease2) printPrereleaseGuidance(detectInstallContext());
16329
16525
  R2.success("oms is up to date.");
16330
16526
  return 0;
16331
16527
  }
16332
16528
  if (comparison > 0) {
16529
+ if (prerelease2) printPrereleaseGuidance(detectInstallContext());
16333
16530
  R2.info("Installed version is newer than the npm registry latest; no downgrade will be performed.");
16334
16531
  return 0;
16335
16532
  }
@@ -16340,6 +16537,11 @@ async function runUpdate(options) {
16340
16537
  R2.warn("Update available.");
16341
16538
  const context = detectInstallContext();
16342
16539
  printInstallContext(context);
16540
+ const prerelease = isPrereleaseVersion(currentVersion);
16541
+ if (prerelease) {
16542
+ R2.info("Selected update channel: stable latest (oh-my-space@latest).");
16543
+ printPrereleaseGuidance(context);
16544
+ }
16343
16545
  if (options.check) {
16344
16546
  if (!context.updateCommand) printGuidance(context);
16345
16547
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-space",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Manage multi-repo workspaces with Git submodules.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/divlook/oh-my-space",
@@ -13,9 +13,18 @@
13
13
  },
14
14
  "keywords": [
15
15
  "oms",
16
- "submodule",
16
+ "oh-my-space",
17
17
  "git",
18
- "cli"
18
+ "cli",
19
+ "git-submodule",
20
+ "submodule",
21
+ "workspace",
22
+ "multi-repo",
23
+ "monorepo",
24
+ "repository-management",
25
+ "developer-tools",
26
+ "ai",
27
+ "agent"
19
28
  ],
20
29
  "author": {
21
30
  "name": "divlook",
@@ -40,7 +49,8 @@
40
49
  "prepack": "npm test",
41
50
  "changeset": "changeset",
42
51
  "version": "changeset version",
43
- "release": "changeset publish"
52
+ "release": "changeset publish",
53
+ "release:beta": "node scripts/publish-beta.mjs"
44
54
  },
45
55
  "devDependencies": {
46
56
  "@changesets/changelog-github": "^0.7.0",