@devdogsuga/backstage 0.1.3 → 0.1.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.
@@ -0,0 +1,7 @@
1
+ //#region ../cli-core/src/db/connection.ts
2
+ /** An env value, with unset and empty both meaning "not given". */
3
+ function nonEmpty(value) {
4
+ return value === void 0 || value === "" ? void 0 : value;
5
+ }
6
+ //#endregion
7
+ export { nonEmpty as t };
package/dist/launch.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { C as discoverRepoRoot, E as setCliName, S as RepoNotFoundError, T as cliName, _ as installFailureLog, d as hasYes, g as stripTierFlag, h as stripNoEnvFlag, m as setNoEnv, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, s as lastSentryEventId } from "./telemetry-Bjoz29Hl.js";
2
- import { c as setMenuEnvHook, d as formatCommand, f as nonEmpty, g as loadEnvSession, h as loadEnvLoad, n as bareGroupStartPath, t as catalog, v as helpPath } from "./catalog-BOBE4ee7.js";
2
+ import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-BuNLUu08.js";
3
+ import { i as loadEnvSession, r as loadEnvLoad } from "./peers-B0KDkI6a.js";
4
+ import { t as nonEmpty } from "./connection-D3XsOE3S.js";
3
5
  import { c as setDryRun, o as isDryRun, s as resolveDryRun } from "./options-BTjOf5KP.js";
4
6
  import { o as unwrap, r as errorMessage } from "./ui-CdKo8mLw.js";
5
7
  import { confirm } from "@clack/prompts";
@@ -320,7 +322,7 @@ async function launchWith(argv, options) {
320
322
  */
321
323
  async function launch(argv) {
322
324
  await launchWith(argv, { dispatch: async (args) => {
323
- const { main } = await import("./cli-ZF4QgJro.js");
325
+ const { main } = await import("./cli-BI5Vn9sR.js");
324
326
  await main(args);
325
327
  } });
326
328
  }
@@ -0,0 +1,229 @@
1
+ import { S as RepoNotFoundError, w as findRepoRoot } from "./telemetry-Bjoz29Hl.js";
2
+ import { createRequire } from "node:module";
3
+ import { pathToFileURL } from "node:url";
4
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ //#region ../cli-core/src/repo/resolve.ts
7
+ /**
8
+ * Resolves an `@devdogsuga/*` package FROM the target repo, not from
9
+ * devtools' own (dlx-isolated) `node_modules`.
10
+ *
11
+ * Ported from the `devtools-dlx` prototype
12
+ * (`/home/sloan/scratchpad/devdogs/prototypes/devtools-dlx/FINDINGS.md`,
13
+ * experiments 2 and 4 — read that file for the two gotchas this module
14
+ * exists to avoid). The short version:
15
+ *
16
+ * - `require.resolve(\`${specifier}/package.json\`)` throws
17
+ * `ERR_PACKAGE_PATH_NOT_EXPORTED` on packages (like `@devdogsuga/env`,
18
+ * `@devdogsuga/open-graph`) whose `exports` map does not list
19
+ * `"./package.json"`, even though the file is on disk.
20
+ * - A resolved path cannot be assumed to contain a literal
21
+ * `node_modules/<specifier>` segment — pnpm workspace-linked packages
22
+ * resolve to their REALPATH (e.g. `packages/open-graph/dist/index.js`),
23
+ * which has no such segment at all.
24
+ * - `require.resolve` ignores whatever `conditions` a caller has set on
25
+ * `tsx`'s `register()` — condition-based resolution (the
26
+ * `devdogs-source` condition) has to be done by hand, reading the
27
+ * target package's own `exports["."]` map.
28
+ *
29
+ * Both problems are solved the same way: walk up from the resolved file's
30
+ * directory to the nearest `package.json` whose own `"name"` field matches
31
+ * the specifier. That works for a real installed dependency AND a
32
+ * workspace symlink, uniformly.
33
+ */
34
+ /**
35
+ * The installable package name a (possibly subpath) specifier belongs to:
36
+ * `@devdogsuga/env/load` → `@devdogsuga/env`, `foo/bar` → `foo`. A repo's
37
+ * package.json declares the PACKAGE as a dependency, never a subpath, so
38
+ * `findDependent` has to search on this rather than the literal specifier a
39
+ * caller wants to import.
40
+ */
41
+ function packageNameOf(specifier) {
42
+ const parts = specifier.split("/");
43
+ return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
44
+ }
45
+ /**
46
+ * Scans `apps/*` and `packages/*` under `repoRoot` for the first
47
+ * `package.json` whose `dependencies`/`devDependencies`/`peerDependencies`
48
+ * names `specifier`'s package (see `packageNameOf`). Returns its path
49
+ * relative to `repoRoot`, or `null` if nothing in the repo depends on it.
50
+ *
51
+ * Re-scans the filesystem on every call — fine for a short-lived CLI
52
+ * invocation calling this a handful of times; see FINDINGS item 5 if this
53
+ * is ever called in a hot loop.
54
+ */
55
+ function findDependent(repoRoot, specifier) {
56
+ const packageName = packageNameOf(specifier);
57
+ for (const group of ["apps", "packages"]) {
58
+ const groupDir = join(repoRoot, group);
59
+ let entries;
60
+ try {
61
+ entries = readdirSync(groupDir);
62
+ } catch {
63
+ continue;
64
+ }
65
+ for (const entry of entries) {
66
+ const pkgJsonPath = join(groupDir, entry, "package.json");
67
+ if (!existsSync(pkgJsonPath)) continue;
68
+ let pkg;
69
+ try {
70
+ pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
71
+ } catch {
72
+ continue;
73
+ }
74
+ if (packageName in {
75
+ ...pkg.dependencies,
76
+ ...pkg.devDependencies,
77
+ ...pkg.peerDependencies
78
+ }) return join(group, entry, "package.json");
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ /** Walks up from `startDir` to the nearest `package.json` whose `"name"` matches `specifier`. */
84
+ function findOwningPackageJson(startDir, specifier) {
85
+ const packageName = packageNameOf(specifier);
86
+ let dir = startDir;
87
+ for (;;) {
88
+ const candidate = join(dir, "package.json");
89
+ if (existsSync(candidate)) try {
90
+ if (JSON.parse(readFileSync(candidate, "utf8")).name === packageName) return candidate;
91
+ } catch {}
92
+ const parent = dirname(dir);
93
+ if (parent === dir) throw new Error(`resolveFromRepo: could not find ${specifier}'s own package.json by walking up from ${startDir}`);
94
+ dir = parent;
95
+ }
96
+ }
97
+ /**
98
+ * Resolves `specifier` as `resolutionBase` (a workspace-relative
99
+ * `package.json` path known to depend on it, from `findDependent()`) would
100
+ * see it.
101
+ *
102
+ * `opts.condition`, when given, is NOT passed through to Node's resolver —
103
+ * `require.resolve` ignores tsx's `register({ conditions })` entirely (see
104
+ * this module's header). Instead the target package's own
105
+ * `exports["."]` map is read directly and `exports["."][condition]` is
106
+ * picked, falling back to `.default`.
107
+ */
108
+ function resolveFromRepo(repoRoot, resolutionBase, specifier, opts) {
109
+ const baseFile = join(repoRoot, resolutionBase);
110
+ const require = createRequire(baseFile);
111
+ let resolvedPath;
112
+ if (opts?.condition) {
113
+ const defaultEntry = require.resolve(specifier);
114
+ const pkgJsonPath = findOwningPackageJson(dirname(defaultEntry), specifier);
115
+ const rootExport = JSON.parse(readFileSync(pkgJsonPath, "utf8")).exports?.["."];
116
+ const conditioned = rootExport && typeof rootExport === "object" ? rootExport[opts.condition] ?? rootExport.default : void 0;
117
+ if (typeof conditioned !== "string") throw new Error(`resolveFromRepo: ${specifier} has no "${opts.condition}" (or "default") export in its exports["."] map`);
118
+ resolvedPath = join(dirname(pkgJsonPath), conditioned);
119
+ } else resolvedPath = require.resolve(specifier);
120
+ const pkgJsonPath = findOwningPackageJson(dirname(resolvedPath), specifier);
121
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
122
+ return {
123
+ resolvedPath,
124
+ version: pkg.version ?? "0.0.0",
125
+ pkgJsonPath
126
+ };
127
+ }
128
+ //#endregion
129
+ //#region ../cli-core/src/repo/peers.ts
130
+ /**
131
+ * Loads devtools' optional-peer `@devdogsuga/*` libraries — the ones
132
+ * published to npm and consumed by the TARGET repo, which devtools reads at
133
+ * runtime rather than bundling its own copy of (see this package's
134
+ * `package.json`: `peerDependencies` + `peerDependenciesMeta.optional`, and
135
+ * `FINDINGS.md` experiment 2).
136
+ *
137
+ * Every export here is memoized per module (one dynamic `import()` per
138
+ * process, not per call) — this is what makes the module-identity guarantee
139
+ * in FINDINGS experiment 3 hold: every devtools call site that needs
140
+ * `@devdogsuga/env`'s registry gets the exact same module instance the
141
+ * repo's own manifests populated via `declare()`/`define()`, because both
142
+ * resolve to the identical absolute file path and Node's ESM cache is keyed
143
+ * by resolved URL.
144
+ */
145
+ const moduleCache = /* @__PURE__ */ new Map();
146
+ /** The file URL each peer resolved to through the target repo, set only on
147
+ * that path (not on either plain bare-specifier fallback). See
148
+ * `repoPeerUrl()`. */
149
+ const resolvedUrls = /* @__PURE__ */ new Map();
150
+ /**
151
+ * Dynamically imports `specifier` as the repo would resolve it, memoized so
152
+ * repeated calls in one process return the SAME module instance (required
153
+ * for the env-registry identity guarantee above).
154
+ *
155
+ * Falls back to a plain bare-specifier `import()` when devtools is not
156
+ * running inside a DevDogsUGA checkout at all (`findRepoRoot()` throws
157
+ * `RepoNotFoundError`). That is not a production affordance — under real
158
+ * `pnpm dlx` use, `specifier` is an optional peer devtools' own package
159
+ * never installs, so the fallback import fails with the ordinary
160
+ * `MODULE_NOT_FOUND` either way. It exists so devtools' OWN test suite
161
+ * (which has no target repo to discover, but does have every one of these
162
+ * peers as a real Backstage workspace package) exercises this code against
163
+ * the genuine package rather than needing every test to mock this module.
164
+ */
165
+ function loadPeer(specifier) {
166
+ const cached = moduleCache.get(specifier);
167
+ if (cached) return cached;
168
+ const promise = (async () => {
169
+ if (process.env.DEVTOOLS_TEST_REPO_ROOT) return import(specifier);
170
+ let repoRoot;
171
+ try {
172
+ repoRoot = findRepoRoot();
173
+ } catch (err) {
174
+ if (err instanceof RepoNotFoundError) return import(specifier);
175
+ throw err;
176
+ }
177
+ const resolutionBase = findDependent(repoRoot, specifier);
178
+ if (!resolutionBase) throw new Error(`Nothing in this repo depends on ${specifier} — expected an app or package under apps/* or packages/* to declare it (dependencies/devDependencies/peerDependencies).`);
179
+ const { resolvedPath } = resolveFromRepo(repoRoot, resolutionBase, specifier);
180
+ const url = pathToFileURL(resolvedPath).href;
181
+ resolvedUrls.set(specifier, url);
182
+ return import(url);
183
+ })();
184
+ moduleCache.set(specifier, promise);
185
+ return promise;
186
+ }
187
+ /**
188
+ * The file URL `specifier` was loaded from through the target repo, or
189
+ * undefined if it has not been loaded yet or came from a plain bare-specifier
190
+ * `import()` (the test-suite and not-in-a-repo fallbacks above).
191
+ *
192
+ * For code devtools imports that itself names a peer by bare specifier --
193
+ * today only devtools' own `env.ts` manifest. From an installed copy that
194
+ * specifier cannot resolve at all (an optional peer is never installed next
195
+ * to devtools under `pnpm dlx`), and even where it can, the only copy that
196
+ * keeps module identity is this one. `repo/peer-redirect.ts` uses it to
197
+ * point the import here.
198
+ */
199
+ function repoPeerUrl(specifier) {
200
+ return resolvedUrls.get(specifier);
201
+ }
202
+ let resolvedEnvModule;
203
+ function loadEnv() {
204
+ return loadPeer("@devdogsuga/env").then((mod) => {
205
+ resolvedEnvModule = mod;
206
+ return mod;
207
+ });
208
+ }
209
+ /**
210
+ * The synchronous companion to `loadEnv()`, for the handful of call sites
211
+ * (`gh/environments.ts`'s `GITHUB_ENVIRONMENT_SPECS` getters) that cannot
212
+ * become async without changing their own callers' contract — they read a
213
+ * plain array off a getter, not a Promise. Safe ONLY after `loadEnv()` has
214
+ * resolved at least once; every caller of these getters already requires
215
+ * `env/discovery.ts`'s `loadRegistry()` (which calls `loadEnv()` itself) to
216
+ * have run first, via `assertRegistryLoaded()`'s own guard.
217
+ */
218
+ function getEnvSync() {
219
+ if (!resolvedEnvModule) throw new Error("getEnvSync() called before loadEnv() ever resolved — call loadRegistry() (env/discovery.ts) first.");
220
+ return resolvedEnvModule;
221
+ }
222
+ function loadEnvLoad() {
223
+ return loadPeer("@devdogsuga/env/load");
224
+ }
225
+ function loadEnvSession() {
226
+ return loadPeer("@devdogsuga/env/session");
227
+ }
228
+ //#endregion
229
+ export { repoPeerUrl as a, loadEnvSession as i, loadEnv as n, loadEnvLoad as r, getEnvSync as t };