@devdogsuga/devtools 0.1.49 → 0.1.51-next.526

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,354 +0,0 @@
1
- import { r as findRepoRoot, t as RepoNotFoundError } from "./root-CjPWNkBu.js";
2
- import { createRequire } from "node:module";
3
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- /** The directory (relative to a Backstage root) holding the DevDogsUGA checkout. */
7
- const DEVDOGSUGA_LINK = "devdogsuga";
8
- var DevdogsugaMissingError = class extends Error {
9
- constructor(path) {
10
- super(`The DevDogsUGA checkout is missing at ${path}. Run \`pnpm devdogsuga\` (links the sibling clone, or \$DEVDOGSUGA_DIR) or \`pnpm install\` in the Backstage root first.`);
11
- this.name = "DevdogsugaMissingError";
12
- }
13
- };
14
- /** The kind of repo `dir` is the root of, by its `package.json` name; null if neither. */
15
- function repoKindOf(dir) {
16
- try {
17
- const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
18
- if (pkg.name === "devdogs-monorepo") return "devdogsuga";
19
- if (pkg.name === "backstage") return "backstage";
20
- } catch {}
21
- return null;
22
- }
23
- function isDir(path) {
24
- try {
25
- return statSync(path).isDirectory();
26
- } catch {
27
- return false;
28
- }
29
- }
30
- /**
31
- * The layout of the repo rooted at `root`. Pure: nothing is memoized, so tests
32
- * can build fixtures freely. An unrecognised package name is treated as
33
- * DevDogsUGA (the contract fixtures and every pre-Backstage caller).
34
- */
35
- function layoutAt(root) {
36
- const kind = repoKindOf(root) === "backstage" ? "backstage" : "devdogsuga";
37
- const sibling = join(root, DEVDOGSUGA_LINK);
38
- const hasDevdogsuga = kind === "devdogsuga" || isDir(sibling);
39
- const devdogsugaRoot = kind === "devdogsuga" ? root : sibling;
40
- const appsDirs = [join(root, "apps")];
41
- if (kind === "backstage" && hasDevdogsuga) appsDirs.push(join(devdogsugaRoot, "apps"));
42
- return {
43
- kind,
44
- root,
45
- get devdogsugaRoot() {
46
- if (!hasDevdogsuga) throw new DevdogsugaMissingError(sibling);
47
- return devdogsugaRoot;
48
- },
49
- ...kind === "backstage" ? { backstageRoot: root } : {},
50
- hasDevdogsuga,
51
- appsDirs: appsDirs.filter(isDir),
52
- envMirrors: kind === "backstage" && hasDevdogsuga ? [devdogsugaRoot] : []
53
- };
54
- }
55
- /** `layout.devdogsugaRoot`, or null instead of a throw when the checkout is missing. */
56
- function devdogsugaRootOrNull(layout) {
57
- return layout.hasDevdogsuga ? layout.devdogsugaRoot : null;
58
- }
59
- let cached;
60
- let cachedFor;
61
- /** The layout of the repo the process is in (`findRepoRoot()`), memoized per root. */
62
- function resolveLayout() {
63
- const root = findRepoRoot();
64
- if (cached === void 0 || cachedFor !== root) {
65
- cached = layoutAt(root);
66
- cachedFor = root;
67
- }
68
- return cached;
69
- }
70
- /** Every directory under `appsDirs` (one app each), de-duplicated by name, sorted by name. */
71
- function listApps(layout = resolveLayout()) {
72
- const seen = /* @__PURE__ */ new Map();
73
- for (const parent of layout.appsDirs) for (const entry of readdirSync(parent, { withFileTypes: true })) {
74
- if (!entry.isDirectory()) continue;
75
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
76
- if (!seen.has(entry.name)) seen.set(entry.name, {
77
- name: entry.name,
78
- dir: join(parent, entry.name)
79
- });
80
- }
81
- return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
82
- }
83
- /** The directory of the app called `name`, or undefined when no repo has it. */
84
- function findApp(name, layout = resolveLayout()) {
85
- return listApps(layout).find((app) => app.name === name)?.dir;
86
- }
87
- /**
88
- * Resolves a workspace-relative path such as `workers.json`'s
89
- * `apps/schedule-builder` to a directory: the repo you are in first, then
90
- * DevDogsUGA's. When neither has it the first candidate is returned, so
91
- * callers get an ordinary ENOENT naming the path rather than a second error.
92
- */
93
- function resolveAppPath(relative, layout = resolveLayout()) {
94
- const first = join(layout.root, relative);
95
- if (existsSync(first)) return first;
96
- if (layout.kind === "backstage" && layout.hasDevdogsuga) {
97
- const second = join(layout.devdogsugaRoot, relative);
98
- if (existsSync(second)) return second;
99
- }
100
- return first;
101
- }
102
- /**
103
- * The pnpm workspace root holding the app called `name`: where
104
- * `pnpm --filter <name>` has to run. DevDogsUGA's root for its apps seen from a
105
- * Backstage checkout, `layout.root` otherwise.
106
- */
107
- function appWorkspaceRoot(name, layout = resolveLayout()) {
108
- const dir = findApp(name, layout);
109
- return dir === void 0 ? layout.root : dirname(dirname(dir));
110
- }
111
- //#endregion
112
- //#region ../cli-core/src/repo/resolve.ts
113
- /**
114
- * Resolves an `@devdogsuga/*` package FROM the target repo, not from
115
- * devtools' own (dlx-isolated) `node_modules`.
116
- *
117
- * Ported from the `devtools-dlx` prototype
118
- * (`/home/sloan/scratchpad/devdogs/prototypes/devtools-dlx/FINDINGS.md`,
119
- * experiments 2 and 4 — read that file for the two gotchas this module
120
- * exists to avoid). The short version:
121
- *
122
- * - `require.resolve(\`${specifier}/package.json\`)` throws
123
- * `ERR_PACKAGE_PATH_NOT_EXPORTED` on packages (like `@devdogsuga/env`,
124
- * `@devdogsuga/open-graph`) whose `exports` map does not list
125
- * `"./package.json"`, even though the file is on disk.
126
- * - A resolved path cannot be assumed to contain a literal
127
- * `node_modules/<specifier>` segment — pnpm workspace-linked packages
128
- * resolve to their REALPATH (e.g. `packages/open-graph/dist/index.js`),
129
- * which has no such segment at all.
130
- * - `require.resolve` ignores whatever `conditions` a caller has set on
131
- * `tsx`'s `register()` — condition-based resolution (the
132
- * `devdogs-source` condition) has to be done by hand, reading the
133
- * target package's own `exports["."]` map.
134
- *
135
- * Both problems are solved the same way: walk up from the resolved file's
136
- * directory to the nearest `package.json` whose own `"name"` field matches
137
- * the specifier. That works for a real installed dependency AND a
138
- * workspace symlink, uniformly.
139
- */
140
- /**
141
- * The installable package name a (possibly subpath) specifier belongs to:
142
- * `@devdogsuga/env/load` → `@devdogsuga/env`, `foo/bar` → `foo`. A repo's
143
- * package.json declares the PACKAGE as a dependency, never a subpath, so
144
- * `findDependent` has to search on this rather than the literal specifier a
145
- * caller wants to import.
146
- */
147
- function packageNameOf(specifier) {
148
- const parts = specifier.split("/");
149
- return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
150
- }
151
- /**
152
- * Scans `apps/*` and `packages/*` under `repoRoot` for the first
153
- * `package.json` whose `dependencies`/`devDependencies`/`peerDependencies`
154
- * names `specifier`'s package (see `packageNameOf`). Returns its path
155
- * relative to `repoRoot`, or `null` if nothing in the repo depends on it.
156
- *
157
- * Re-scans the filesystem on every call — fine for a short-lived CLI
158
- * invocation calling this a handful of times; see FINDINGS item 5 if this
159
- * is ever called in a hot loop.
160
- */
161
- function findDependent(repoRoot, specifier) {
162
- const packageName = packageNameOf(specifier);
163
- for (const group of ["apps", "packages"]) {
164
- const groupDir = join(repoRoot, group);
165
- let entries;
166
- try {
167
- entries = readdirSync(groupDir);
168
- } catch {
169
- continue;
170
- }
171
- for (const entry of entries) {
172
- const pkgJsonPath = join(groupDir, entry, "package.json");
173
- if (!existsSync(pkgJsonPath)) continue;
174
- let pkg;
175
- try {
176
- pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
177
- } catch {
178
- continue;
179
- }
180
- if (packageName in {
181
- ...pkg.dependencies,
182
- ...pkg.devDependencies,
183
- ...pkg.peerDependencies
184
- }) return join(group, entry, "package.json");
185
- }
186
- }
187
- return null;
188
- }
189
- /** Walks up from `startDir` to the nearest `package.json` whose `"name"` matches `specifier`. */
190
- function findOwningPackageJson(startDir, specifier) {
191
- const packageName = packageNameOf(specifier);
192
- let dir = startDir;
193
- for (;;) {
194
- const candidate = join(dir, "package.json");
195
- if (existsSync(candidate)) try {
196
- if (JSON.parse(readFileSync(candidate, "utf8")).name === packageName) return candidate;
197
- } catch {}
198
- const parent = dirname(dir);
199
- if (parent === dir) throw new Error(`resolveFromRepo: could not find ${specifier}'s own package.json by walking up from ${startDir}`);
200
- dir = parent;
201
- }
202
- }
203
- /**
204
- * Resolves `specifier` as `resolutionBase` (a workspace-relative
205
- * `package.json` path known to depend on it, from `findDependent()`) would
206
- * see it.
207
- *
208
- * `opts.condition`, when given, is NOT passed through to Node's resolver —
209
- * `require.resolve` ignores tsx's `register({ conditions })` entirely (see
210
- * this module's header). Instead the target package's own
211
- * `exports["."]` map is read directly and `exports["."][condition]` is
212
- * picked, falling back to `.default`.
213
- */
214
- function resolveFromRepo(repoRoot, resolutionBase, specifier, opts) {
215
- const baseFile = join(repoRoot, resolutionBase);
216
- const require = createRequire(baseFile);
217
- let resolvedPath;
218
- if (opts?.condition) {
219
- const defaultEntry = require.resolve(specifier);
220
- const pkgJsonPath = findOwningPackageJson(dirname(defaultEntry), specifier);
221
- const rootExport = JSON.parse(readFileSync(pkgJsonPath, "utf8")).exports?.["."];
222
- const conditioned = rootExport && typeof rootExport === "object" ? rootExport[opts.condition] ?? rootExport.default : void 0;
223
- if (typeof conditioned !== "string") throw new Error(`resolveFromRepo: ${specifier} has no "${opts.condition}" (or "default") export in its exports["."] map`);
224
- resolvedPath = join(dirname(pkgJsonPath), conditioned);
225
- } else resolvedPath = require.resolve(specifier);
226
- const pkgJsonPath = findOwningPackageJson(dirname(resolvedPath), specifier);
227
- const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
228
- return {
229
- resolvedPath,
230
- version: pkg.version ?? "0.0.0",
231
- pkgJsonPath
232
- };
233
- }
234
- //#endregion
235
- //#region ../cli-core/src/repo/peers.ts
236
- /**
237
- * Loads devtools' optional-peer `@devdogsuga/*` libraries — the ones
238
- * published to npm and consumed by the TARGET repo, which devtools reads at
239
- * runtime rather than bundling its own copy of (see this package's
240
- * `package.json`: `peerDependencies` + `peerDependenciesMeta.optional`, and
241
- * `FINDINGS.md` experiment 2).
242
- *
243
- * Every export here is memoized per module (one dynamic `import()` per
244
- * process, not per call) — this is what makes the module-identity guarantee
245
- * in FINDINGS experiment 3 hold: every devtools call site that needs
246
- * `@devdogsuga/env`'s registry gets the exact same module instance the
247
- * repo's own manifests populated via `declare()`/`define()`, because both
248
- * resolve to the identical absolute file path and Node's ESM cache is keyed
249
- * by resolved URL.
250
- */
251
- const moduleCache = /* @__PURE__ */ new Map();
252
- /** The file URL each peer resolved to through the target repo, set only on
253
- * that path (not on either plain bare-specifier fallback). See
254
- * `repoPeerUrl()`. */
255
- const resolvedUrls = /* @__PURE__ */ new Map();
256
- /**
257
- * Dynamically imports `specifier` as the repo would resolve it, memoized so
258
- * repeated calls in one process return the SAME module instance (required
259
- * for the env-registry identity guarantee above).
260
- *
261
- * Falls back to a plain bare-specifier `import()` when devtools is not
262
- * running inside a DevDogsUGA checkout at all (`findRepoRoot()` throws
263
- * `RepoNotFoundError`). That is not a production affordance — under real
264
- * `pnpm dlx` use, `specifier` is an optional peer devtools' own package
265
- * never installs, so the fallback import fails with the ordinary
266
- * `MODULE_NOT_FOUND` either way. It exists so devtools' OWN test suite
267
- * (which has no target repo to discover, but does have every one of these
268
- * peers as a real Backstage workspace package) exercises this code against
269
- * the genuine package rather than needing every test to mock this module.
270
- */
271
- function loadPeer(specifier) {
272
- const cached = moduleCache.get(specifier);
273
- if (cached) return cached;
274
- const promise = (async () => {
275
- if (process.env.DEVTOOLS_TEST_REPO_ROOT) return import(specifier);
276
- let repoRoot;
277
- try {
278
- repoRoot = findRepoRoot();
279
- } catch (err) {
280
- if (err instanceof RepoNotFoundError) return import(specifier);
281
- throw err;
282
- }
283
- const resolutionBase = findDependent(repoRoot, specifier);
284
- 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).`);
285
- const { resolvedPath } = resolveFromRepo(repoRoot, resolutionBase, specifier);
286
- const url = pathToFileURL(resolvedPath).href;
287
- resolvedUrls.set(specifier, url);
288
- return import(url);
289
- })();
290
- moduleCache.set(specifier, promise);
291
- return promise;
292
- }
293
- /**
294
- * The file URL `specifier` was loaded from through the target repo, or
295
- * undefined if it has not been loaded yet or came from a plain bare-specifier
296
- * `import()` (the test-suite and not-in-a-repo fallbacks above).
297
- *
298
- * For code devtools imports that itself names a peer by bare specifier --
299
- * today only devtools' own `env.ts` manifest. From an installed copy that
300
- * specifier cannot resolve at all (an optional peer is never installed next
301
- * to devtools under `pnpm dlx`), and even where it can, the only copy that
302
- * keeps module identity is this one. `repo/peer-redirect.ts` uses it to
303
- * point the import here.
304
- */
305
- function repoPeerUrl(specifier) {
306
- return resolvedUrls.get(specifier);
307
- }
308
- /**
309
- * Like `repoPeerUrl`, but for any specifier a package of the repo you are in
310
- * can resolve, subpaths included (`@devdogsuga/env/nextjs`), without having
311
- * loaded it. Undefined outside a real repo or when nothing depends on it.
312
- */
313
- function resolveRepoPeerUrl(specifier) {
314
- const known = resolvedUrls.get(specifier);
315
- if (known) return known;
316
- if (process.env.DEVTOOLS_TEST_REPO_ROOT) return void 0;
317
- try {
318
- const repoRoot = findRepoRoot();
319
- const base = findDependent(repoRoot, specifier);
320
- if (!base) return void 0;
321
- const { resolvedPath } = resolveFromRepo(repoRoot, base, specifier);
322
- return pathToFileURL(resolvedPath).href;
323
- } catch {
324
- return;
325
- }
326
- }
327
- let resolvedEnvModule;
328
- function loadEnv() {
329
- return loadPeer("@devdogsuga/env").then((mod) => {
330
- resolvedEnvModule = mod;
331
- return mod;
332
- });
333
- }
334
- /**
335
- * The synchronous companion to `loadEnv()`, for the handful of call sites
336
- * (`gh/environments.ts`'s `GITHUB_ENVIRONMENT_SPECS` getters) that cannot
337
- * become async without changing their own callers' contract — they read a
338
- * plain array off a getter, not a Promise. Safe ONLY after `loadEnv()` has
339
- * resolved at least once; every caller of these getters already requires
340
- * `env/discovery.ts`'s `loadRegistry()` (which calls `loadEnv()` itself) to
341
- * have run first, via `assertRegistryLoaded()`'s own guard.
342
- */
343
- function getEnvSync() {
344
- if (!resolvedEnvModule) throw new Error("getEnvSync() called before loadEnv() ever resolved — call loadRegistry() (env/discovery.ts) first.");
345
- return resolvedEnvModule;
346
- }
347
- function loadEnvLoad() {
348
- return loadPeer("@devdogsuga/env/load");
349
- }
350
- function loadEnvSession() {
351
- return loadPeer("@devdogsuga/env/session");
352
- }
353
- //#endregion
354
- export { repoPeerUrl as a, devdogsugaRootOrNull as c, listApps as d, resolveAppPath as f, loadEnvSession as i, findApp as l, loadEnv as n, resolveRepoPeerUrl as o, resolveLayout as p, loadEnvLoad as r, appWorkspaceRoot as s, getEnvSync as t, layoutAt as u };
@@ -1,99 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- //#region ../cli-core/src/version.ts
5
- let cachedOwnDir;
6
- /** The published package's own root: the nearest directory above this
7
- * module's file that holds a `package.json`. A bundled CLI runs from a flat
8
- * `dist/`, so that is the CLI's root (`dist/launch.js` -> up one); the core
9
- * is inlined into it, so its own source location never matters at run time.
10
- * Memoized since it never changes within a process. */
11
- function ownPackageDir() {
12
- if (cachedOwnDir !== void 0) return cachedOwnDir;
13
- let dir = dirname(fileURLToPath(import.meta.url));
14
- while (!existsSync(join(dir, "package.json")) && dirname(dir) !== dir) dir = dirname(dir);
15
- cachedOwnDir = dir;
16
- return dir;
17
- }
18
- /** Reads this build's own `package.json` version — used by `telemetry.ts`
19
- * (release tagging) and the CLI's `version` command. */
20
- function ownVersion() {
21
- const pkg = JSON.parse(readFileSync(join(ownPackageDir(), "package.json"), "utf8"));
22
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
23
- }
24
- //#endregion
25
- //#region ../cli-core/src/repo/root.ts
26
- /**
27
- * Where devtools finds the DevDogsUGA checkout it is running against.
28
- *
29
- * devtools used to live inside `packages/devtools` of that repo, so its own
30
- * `import.meta.url` WAS a path into the repo (`PROJECT_ROOT` /
31
- * `REPO_ROOT` walked up a fixed number of `..` segments from this file).
32
- * Published as its own package and run through `pnpm dlx`, that assumption
33
- * breaks: this file's `import.meta.url` points into a dlx cache directory
34
- * or an isolated `node_modules` tree that has nothing to do with the repo
35
- * the contributor is standing in.
36
- *
37
- * So repo discovery walks up from `process.cwd()` instead, looking for the
38
- * marker validated in the `devtools-dlx` prototype
39
- * (`/home/sloan/scratchpad/devdogs/prototypes/devtools-dlx/FINDINGS.md`,
40
- * experiment 1): a directory containing `pnpm-workspace.yaml` AND whose
41
- * `package.json` has `"name": "devdogs-monorepo"` (DevDogsUGA) or
42
- * `"backstage"` (Backstage, which the CLIs also run from; `layout.ts` says
43
- * what differs). The name check is not redundant — a bare
44
- * `pnpm-workspace.yaml` matches any pnpm workspace, which devtools should
45
- * refuse rather than silently "find".
46
- */
47
- /**
48
- * The `package.json` names a repo root may carry: DevDogsUGA's, and Backstage's
49
- * (the CLIs run from both; see `layout.ts`).
50
- */
51
- const REPO_MARKER_NAMES = ["devdogs-monorepo", "backstage"];
52
- var RepoNotFoundError = class extends Error {
53
- constructor() {
54
- super("run this from inside a DevDogsUGA clone");
55
- this.name = "RepoNotFoundError";
56
- }
57
- };
58
- function looksLikeRepoRoot(dir) {
59
- if (!existsSync(join(dir, "pnpm-workspace.yaml"))) return false;
60
- try {
61
- const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
62
- return REPO_MARKER_NAMES.includes(pkg.name);
63
- } catch {
64
- return false;
65
- }
66
- }
67
- /**
68
- * Walks up from `startDir` (default `process.cwd()`) looking for the repo
69
- * marker. Returns `null` rather than throwing — `findRepoRoot()` below is
70
- * the throwing convenience most callers want; this is exported for callers
71
- * (like `setup`) that need to know without failing.
72
- */
73
- function discoverRepoRoot(startDir = process.cwd()) {
74
- let dir = startDir;
75
- for (;;) {
76
- if (looksLikeRepoRoot(dir)) return dir;
77
- const parent = dirname(dir);
78
- if (parent === dir) return null;
79
- dir = parent;
80
- }
81
- }
82
- let cachedRoot;
83
- /**
84
- * The memoized, throwing repo-root lookup. Lazy on purpose: importing this
85
- * module must never fail just because nothing has called it yet (`setup`
86
- * and `completions` in `launch.ts` never need a repo at all, and must stay
87
- * that way — see that file's header comment on why).
88
- *
89
- * Resolved once per process: `process.cwd()` does not change mid-invocation
90
- * for this CLI, and re-walking the filesystem on every call would be pure
91
- * waste.
92
- */
93
- function findRepoRoot() {
94
- if (cachedRoot === void 0) cachedRoot = process.env.DEVTOOLS_TEST_REPO_ROOT ?? discoverRepoRoot();
95
- if (cachedRoot === null) throw new RepoNotFoundError();
96
- return cachedRoot;
97
- }
98
- //#endregion
99
- export { ownVersion as a, ownPackageDir as i, discoverRepoRoot as n, findRepoRoot as r, RepoNotFoundError as t };
@@ -1,2 +0,0 @@
1
- import { t as runStackCommand } from "./stack-C_50FafG.js";
2
- export { runStackCommand };