@gjsify/cli 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.gjs.mjs +166 -173
- package/lib/bundler-pick.js +10 -2
- package/lib/commands/foreach.d.ts +2 -1
- package/lib/commands/foreach.js +107 -34
- package/lib/commands/install.js +179 -1
- package/lib/commands/run.js +6 -26
- package/lib/commands/workspace.js +11 -1
- package/lib/utils/install-backend-native.js +242 -60
- package/lib/utils/workspace-root.d.ts +1 -0
- package/lib/utils/workspace-root.js +46 -0
- package/package.json +68 -66
|
@@ -5,17 +5,23 @@
|
|
|
5
5
|
// node_modules/ via @gjsify/tar. Output layout matches `npm install` so the
|
|
6
6
|
// existing `runGjsBundle()` prebuild detection works without branching.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
8
|
+
// Phase D.7b — version-conflict resolution via nested `node_modules`.
|
|
9
|
+
// The resolver tracks per-package placement: a dep is hoisted to the
|
|
10
|
+
// root when no conflict exists, nested under the requesting package
|
|
11
|
+
// when its required version is incompatible with what's already at
|
|
12
|
+
// the root. Mirrors npm v3+ behavior.
|
|
13
|
+
//
|
|
14
|
+
// Out of scope (still deferred): peerDependencies validation,
|
|
9
15
|
// lifecycle scripts, git/file specs.
|
|
10
16
|
import * as fs from "node:fs";
|
|
11
17
|
import * as path from "node:path";
|
|
12
18
|
import * as os from "node:os";
|
|
13
|
-
import { Range, SemVer, maxSatisfying, } from "@gjsify/semver";
|
|
19
|
+
import { Range, SemVer, maxSatisfying, satisfies, } from "@gjsify/semver";
|
|
14
20
|
import { DEFAULT_REGISTRY, fetchPackument, fetchTarball, parseNpmrc, } from "@gjsify/npm-registry";
|
|
15
21
|
import { extractTarball } from "@gjsify/tar";
|
|
16
22
|
const DEFAULT_CONCURRENCY = Number(process.env.GJSIFY_INSTALL_CONCURRENCY ?? "8") || 8;
|
|
17
23
|
const LOCKFILE_NAME = "gjsify-lock.json";
|
|
18
|
-
const LOCKFILE_VERSION =
|
|
24
|
+
const LOCKFILE_VERSION = 2;
|
|
19
25
|
export async function installPackagesNative(opts) {
|
|
20
26
|
if (opts.specs.length === 0) {
|
|
21
27
|
throw new Error("installPackagesNative: empty specs list");
|
|
@@ -66,7 +72,13 @@ export async function installPackagesNative(opts) {
|
|
|
66
72
|
return topLevelResolutions(opts.specs, nodes);
|
|
67
73
|
}
|
|
68
74
|
function topLevelResolutions(specs, nodes) {
|
|
69
|
-
|
|
75
|
+
// Top-level installs live at `node_modules/<name>` (no nesting). Build
|
|
76
|
+
// a name → root-node lookup limited to the top-level set.
|
|
77
|
+
const byName = new Map();
|
|
78
|
+
for (const n of nodes) {
|
|
79
|
+
if (n.installPath === `node_modules/${n.name}`)
|
|
80
|
+
byName.set(n.name, n);
|
|
81
|
+
}
|
|
70
82
|
const out = [];
|
|
71
83
|
for (const spec of specs) {
|
|
72
84
|
const name = parseSpecName(spec);
|
|
@@ -87,6 +99,21 @@ function parseSpecName(spec) {
|
|
|
87
99
|
const at = spec.indexOf("@");
|
|
88
100
|
return at === -1 ? spec : spec.slice(0, at);
|
|
89
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Tree-aware dependency resolution with npm v3+ hoisting semantics.
|
|
104
|
+
*
|
|
105
|
+
* - A dep is HOISTED (placed at `node_modules/<dep>`) when no existing
|
|
106
|
+
* placement conflicts with its required range — either it's not
|
|
107
|
+
* placed yet, or it's already at the root with a satisfying version.
|
|
108
|
+
* - A dep is NESTED (placed at `<requester>/node_modules/<dep>`) when
|
|
109
|
+
* the root has an incompatible version. Subsequent dependents of the
|
|
110
|
+
* same conflicting version reuse the nested placement.
|
|
111
|
+
*
|
|
112
|
+
* The walk is BFS over (requester, depName, depRange) edges. Top-level
|
|
113
|
+
* specs are seeded with a synthetic `null` requester so they hoist to
|
|
114
|
+
* the root. Each placement returns a `ResolvedNode` whose `installPath`
|
|
115
|
+
* captures where it lives in the tree.
|
|
116
|
+
*/
|
|
90
117
|
async function resolveDeps(specs, npmrc, log) {
|
|
91
118
|
const packumentCache = new Map();
|
|
92
119
|
const fetchPkg = (name) => {
|
|
@@ -97,45 +124,155 @@ async function resolveDeps(specs, npmrc, log) {
|
|
|
97
124
|
packumentCache.set(name, fresh);
|
|
98
125
|
return fresh;
|
|
99
126
|
};
|
|
100
|
-
|
|
101
|
-
const
|
|
127
|
+
/** Every installed package keyed by `installPath`. */
|
|
128
|
+
const byPath = new Map();
|
|
129
|
+
/** Root placements indexed by name for the hoist-vs-nest decision. */
|
|
130
|
+
const root = new Map();
|
|
131
|
+
const queue = specs.map(parseSpec).map((s) => ({
|
|
132
|
+
from: null,
|
|
133
|
+
name: s.name,
|
|
134
|
+
range: s.range,
|
|
135
|
+
required: true,
|
|
136
|
+
}));
|
|
102
137
|
while (queue.length > 0) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
138
|
+
const edge = queue.shift();
|
|
139
|
+
// Walk the ancestor chain to see whether a satisfying placement is
|
|
140
|
+
// already visible from the requester's `node_modules` lookup. npm's
|
|
141
|
+
// resolver does this — each level of nesting acts as a fallback.
|
|
142
|
+
const visible = findVisible(edge.from, edge.name, byPath);
|
|
143
|
+
if (visible && satisfiesRange(visible.version, edge.range)) {
|
|
144
|
+
// Compatible placement reachable; reuse, no new install.
|
|
107
145
|
continue;
|
|
108
146
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
147
|
+
// No compatible existing placement. Resolve a fresh version.
|
|
148
|
+
let version = null;
|
|
149
|
+
try {
|
|
150
|
+
const packument = await fetchPkg(edge.name);
|
|
151
|
+
version = pickVersion(packument, edge.range);
|
|
152
|
+
if (!version) {
|
|
153
|
+
if (!edge.required)
|
|
154
|
+
continue;
|
|
155
|
+
throw new Error(`No version of ${edge.name} satisfies ${edge.range}`);
|
|
156
|
+
}
|
|
157
|
+
const v = packument.versions[version];
|
|
158
|
+
if (!v) {
|
|
159
|
+
throw new Error(`Packument for ${edge.name} promised ${version} but no entry exists`);
|
|
160
|
+
}
|
|
161
|
+
// Decision: hoist to root, or nest under the requester?
|
|
162
|
+
// - Hoist iff the root has no conflicting placement (i.e. the
|
|
163
|
+
// root slot for `name` is empty OR holds the same version).
|
|
164
|
+
// - Otherwise nest. Top-level specs (from === null) always
|
|
165
|
+
// hoist; the resolver guarantees they never conflict with
|
|
166
|
+
// each other because the input set is checked once.
|
|
167
|
+
const installPath = decidePlacement(edge.from, edge.name, version, root);
|
|
168
|
+
const node = {
|
|
169
|
+
name: edge.name,
|
|
170
|
+
version,
|
|
171
|
+
tarballUrl: v.dist.tarball,
|
|
172
|
+
integrity: v.dist.integrity,
|
|
173
|
+
installPath,
|
|
174
|
+
dependencies: v.dependencies ?? {},
|
|
175
|
+
optionalDependencies: v.optionalDependencies ?? {},
|
|
176
|
+
bin: v.bin,
|
|
177
|
+
};
|
|
178
|
+
byPath.set(installPath, node);
|
|
179
|
+
if (installPath === `node_modules/${edge.name}`) {
|
|
180
|
+
root.set(edge.name, node);
|
|
181
|
+
}
|
|
182
|
+
log("resolve: %s@%s ← %s (at %s)", edge.name, version, edge.range, installPath);
|
|
183
|
+
for (const [depName, depRange] of Object.entries(node.dependencies)) {
|
|
184
|
+
queue.push({ from: installPath, name: depName, range: depRange, required: true });
|
|
185
|
+
}
|
|
186
|
+
for (const [depName, depRange] of Object.entries(node.optionalDependencies)) {
|
|
187
|
+
queue.push({ from: installPath, name: depName, range: depRange, required: false });
|
|
188
|
+
}
|
|
117
189
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
127
|
-
resolved.set(spec.name, node);
|
|
128
|
-
log("resolve: %s@%s ← %s", spec.name, version, spec.range);
|
|
129
|
-
for (const [depName, depRange] of Object.entries(node.dependencies)) {
|
|
130
|
-
if (!resolved.has(depName))
|
|
131
|
-
queue.push({ name: depName, range: depRange });
|
|
190
|
+
catch (e) {
|
|
191
|
+
// Optional deps that fail to resolve are skipped — yarn/npm
|
|
192
|
+
// behavior. Required deps re-throw.
|
|
193
|
+
if (!edge.required) {
|
|
194
|
+
log("resolve: optional dep %s@%s skipped (%s)", edge.name, edge.range, e.message);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
throw e;
|
|
132
198
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
199
|
+
}
|
|
200
|
+
return Array.from(byPath.values());
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Walk the ancestor `node_modules` chain from `requesterPath` upward,
|
|
204
|
+
* looking for a placement of `name` that the requester would resolve
|
|
205
|
+
* through Node's CommonJS lookup. Returns the first match — that's the
|
|
206
|
+
* one the requester actually sees at runtime.
|
|
207
|
+
*/
|
|
208
|
+
function findVisible(requesterPath, name, byPath) {
|
|
209
|
+
// From the requester's directory, Node walks up node_modules dirs
|
|
210
|
+
// looking for `<dir>/node_modules/<name>`. Translate that to lockfile
|
|
211
|
+
// paths: any prefix of the requester's `installPath` that ends in a
|
|
212
|
+
// package directory gives a candidate `<prefix>/node_modules/<name>`.
|
|
213
|
+
//
|
|
214
|
+
// The requester itself ALSO checks its OWN `node_modules` first
|
|
215
|
+
// (i.e. `<requesterPath>/node_modules/<name>` — nested deps shadow
|
|
216
|
+
// ancestor ones). Then it walks up.
|
|
217
|
+
const candidates = [];
|
|
218
|
+
if (requesterPath !== null) {
|
|
219
|
+
candidates.push(`${requesterPath}/node_modules/${name}`);
|
|
220
|
+
// Walk up: strip the last `/node_modules/<pkg>` segment and try again.
|
|
221
|
+
let p = requesterPath;
|
|
222
|
+
// eslint-disable-next-line no-constant-condition
|
|
223
|
+
while (true) {
|
|
224
|
+
// Find the deepest `/node_modules/<pkg>` in `p`, strip it.
|
|
225
|
+
const idx = p.lastIndexOf("/node_modules/");
|
|
226
|
+
if (idx < 0)
|
|
227
|
+
break;
|
|
228
|
+
p = p.slice(0, idx);
|
|
229
|
+
candidates.push(`${p}/node_modules/${name}`);
|
|
230
|
+
if (p === "")
|
|
231
|
+
break;
|
|
136
232
|
}
|
|
137
233
|
}
|
|
138
|
-
|
|
234
|
+
// The root `node_modules/<name>` is the final candidate (covers the
|
|
235
|
+
// `requesterPath === null` case too).
|
|
236
|
+
candidates.push(`node_modules/${name}`);
|
|
237
|
+
for (const candidate of candidates) {
|
|
238
|
+
const hit = byPath.get(candidate);
|
|
239
|
+
if (hit)
|
|
240
|
+
return hit;
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Decide where to install `name@version` for a request from `requesterPath`.
|
|
246
|
+
*
|
|
247
|
+
* - Root is empty for `name`: hoist (return `node_modules/<name>`).
|
|
248
|
+
* - Root has the SAME version: reuse the root placement.
|
|
249
|
+
* - Root has a DIFFERENT version: nest under the requester.
|
|
250
|
+
*
|
|
251
|
+
* Top-level requesters (requesterPath === null) always hoist.
|
|
252
|
+
*/
|
|
253
|
+
function decidePlacement(requesterPath, name, version, root) {
|
|
254
|
+
const rootSlot = root.get(name);
|
|
255
|
+
if (!rootSlot)
|
|
256
|
+
return `node_modules/${name}`;
|
|
257
|
+
if (rootSlot.version === version)
|
|
258
|
+
return `node_modules/${name}`;
|
|
259
|
+
if (requesterPath === null) {
|
|
260
|
+
// Top-level specs are deduplicated by the caller before reaching
|
|
261
|
+
// here; this branch is defensive (would only fire on a duplicate
|
|
262
|
+
// top-level spec with conflicting versions).
|
|
263
|
+
return `node_modules/${name}`;
|
|
264
|
+
}
|
|
265
|
+
return `${requesterPath}/node_modules/${name}`;
|
|
266
|
+
}
|
|
267
|
+
function satisfiesRange(version, range) {
|
|
268
|
+
// dist-tag (e.g. `latest`) cannot be matched here — caller passed a
|
|
269
|
+
// raw range. Dist-tags only meaningful at fresh-resolve time.
|
|
270
|
+
try {
|
|
271
|
+
return satisfies(version, new Range(range));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
139
276
|
}
|
|
140
277
|
function readLockfile(lockfilePath) {
|
|
141
278
|
if (!fs.existsSync(lockfilePath))
|
|
@@ -154,10 +291,10 @@ function readLockfile(lockfilePath) {
|
|
|
154
291
|
}
|
|
155
292
|
function writeLockfile(lockfilePath, specs, nodes) {
|
|
156
293
|
const packages = {};
|
|
157
|
-
// Sort for deterministic
|
|
158
|
-
const sorted = [...nodes].sort((a, b) =>
|
|
294
|
+
// Sort by install path for deterministic, diff-friendly output.
|
|
295
|
+
const sorted = [...nodes].sort((a, b) => a.installPath < b.installPath ? -1 : a.installPath > b.installPath ? 1 : 0);
|
|
159
296
|
for (const node of sorted) {
|
|
160
|
-
packages[node.
|
|
297
|
+
packages[node.installPath] = {
|
|
161
298
|
version: node.version,
|
|
162
299
|
resolved: node.tarballUrl,
|
|
163
300
|
integrity: node.integrity,
|
|
@@ -173,16 +310,26 @@ function writeLockfile(lockfilePath, specs, nodes) {
|
|
|
173
310
|
fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) + "\n");
|
|
174
311
|
}
|
|
175
312
|
function lockfileToNodes(lockfile) {
|
|
176
|
-
return Object.entries(lockfile.packages).map(([
|
|
177
|
-
name
|
|
313
|
+
return Object.entries(lockfile.packages).map(([installPath, entry]) => ({
|
|
314
|
+
// Recover the package name from the path: the last segment is
|
|
315
|
+
// either `<name>` (unscoped) or `@scope/<name>` (scoped).
|
|
316
|
+
name: nameFromInstallPath(installPath),
|
|
178
317
|
version: entry.version,
|
|
179
318
|
tarballUrl: entry.resolved,
|
|
180
319
|
integrity: entry.integrity,
|
|
320
|
+
installPath,
|
|
181
321
|
dependencies: entry.dependencies ?? {},
|
|
182
322
|
optionalDependencies: {},
|
|
183
323
|
bin: entry.bin,
|
|
184
324
|
}));
|
|
185
325
|
}
|
|
326
|
+
function nameFromInstallPath(installPath) {
|
|
327
|
+
// Last `node_modules/` boundary, then the rest is the package name
|
|
328
|
+
// (single segment unscoped, or `@scope/pkg` scoped).
|
|
329
|
+
const idx = installPath.lastIndexOf("/node_modules/");
|
|
330
|
+
const after = idx < 0 ? installPath.replace(/^node_modules\//, "") : installPath.slice(idx + "/node_modules/".length);
|
|
331
|
+
return after;
|
|
332
|
+
}
|
|
186
333
|
function lockfileMatchesRequest(lockfile, specs) {
|
|
187
334
|
if (lockfile.requested.length !== specs.length)
|
|
188
335
|
return false;
|
|
@@ -255,42 +402,77 @@ function pickVersion(packument, range) {
|
|
|
255
402
|
return maxSatisfying(versions, parsedRange);
|
|
256
403
|
}
|
|
257
404
|
async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
|
|
258
|
-
|
|
405
|
+
// Sort by install-path depth ascending so parents extract before
|
|
406
|
+
// children. Extracting a parent on top of an existing child would
|
|
407
|
+
// wipe out the child.
|
|
408
|
+
const queue = [...nodes].sort((a, b) => depth(a.installPath) - depth(b.installPath) ||
|
|
409
|
+
(a.installPath < b.installPath ? -1 : 1));
|
|
259
410
|
const workers = [];
|
|
260
411
|
const concurrency = Math.max(1, Math.min(DEFAULT_CONCURRENCY, queue.length));
|
|
412
|
+
// Parents (depth 1) are extracted serially first to avoid concurrent
|
|
413
|
+
// `rm -rf` + extract races with their children. Once depth-1 is done,
|
|
414
|
+
// depths >=2 run with full concurrency.
|
|
415
|
+
let cursor = 0;
|
|
416
|
+
const depth1End = queue.findIndex((n) => depth(n.installPath) > 1);
|
|
417
|
+
const splitAt = depth1End < 0 ? queue.length : depth1End;
|
|
418
|
+
// Serial root pass.
|
|
419
|
+
while (cursor < splitAt) {
|
|
420
|
+
const node = queue[cursor++];
|
|
421
|
+
if (!node)
|
|
422
|
+
break;
|
|
423
|
+
await extractOne(node, prefix, npmrc, log);
|
|
424
|
+
}
|
|
425
|
+
// Concurrent nested pass.
|
|
261
426
|
for (let i = 0; i < concurrency; i++) {
|
|
262
|
-
workers.push(
|
|
427
|
+
workers.push((async () => {
|
|
428
|
+
while (true) {
|
|
429
|
+
const idx = cursor++;
|
|
430
|
+
if (idx >= queue.length)
|
|
431
|
+
return;
|
|
432
|
+
const node = queue[idx];
|
|
433
|
+
if (!node)
|
|
434
|
+
return;
|
|
435
|
+
await extractOne(node, prefix, npmrc, log);
|
|
436
|
+
}
|
|
437
|
+
})());
|
|
263
438
|
}
|
|
264
439
|
await Promise.all(workers);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
440
|
+
}
|
|
441
|
+
async function extractOne(node, prefix, npmrc, log) {
|
|
442
|
+
const dest = path.join(prefix, node.installPath);
|
|
443
|
+
log("fetch: %s@%s ← %s (→ %s)", node.name, node.version, node.tarballUrl, node.installPath);
|
|
444
|
+
const bytes = await fetchTarball(node.tarballUrl, {
|
|
445
|
+
npmrc,
|
|
446
|
+
integrity: node.integrity,
|
|
447
|
+
});
|
|
448
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
449
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
450
|
+
await extractTarball(bytes, dest);
|
|
451
|
+
}
|
|
452
|
+
function depth(installPath) {
|
|
453
|
+
// Count `node_modules/` segments to know nesting depth.
|
|
454
|
+
// `node_modules/foo` = 1, `node_modules/foo/node_modules/bar` = 2, etc.
|
|
455
|
+
return installPath.split("/node_modules/").length;
|
|
281
456
|
}
|
|
282
457
|
async function linkBins(nodes, prefix, log) {
|
|
458
|
+
// Only root-level packages publish bins into the top-level
|
|
459
|
+
// `node_modules/.bin/`. Nested-package bins are addressable by their
|
|
460
|
+
// direct dependents through the nested .bin (npm matches this) — we
|
|
461
|
+
// omit nested-bin linking for now since no consumer of the install
|
|
462
|
+
// backend depends on it (gjsify's own use cases all hit root bins).
|
|
283
463
|
const binDir = path.join(prefix, "node_modules", ".bin");
|
|
284
464
|
let created = 0;
|
|
285
465
|
for (const node of nodes) {
|
|
286
466
|
if (!node.bin)
|
|
287
467
|
continue;
|
|
468
|
+
if (depth(node.installPath) !== 1)
|
|
469
|
+
continue;
|
|
288
470
|
const map = normalizeBin(node.name, node.bin);
|
|
289
471
|
if (map.size === 0)
|
|
290
472
|
continue;
|
|
291
473
|
fs.mkdirSync(binDir, { recursive: true });
|
|
292
474
|
for (const [binName, binTarget] of map) {
|
|
293
|
-
const targetAbs = path.join(prefix,
|
|
475
|
+
const targetAbs = path.join(prefix, node.installPath, binTarget);
|
|
294
476
|
if (!fs.existsSync(targetAbs))
|
|
295
477
|
continue;
|
|
296
478
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function findWorkspaceRoot(start: string): string | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Walk up from a starting directory to the first ancestor whose
|
|
2
|
+
// `package.json` declares a `workspaces` field — the monorepo root.
|
|
3
|
+
//
|
|
4
|
+
// Used by `gjsify run`, `gjsify workspace`, and `gjsify foreach`: each
|
|
5
|
+
// can be invoked from inside any workspace (chained script calls put the
|
|
6
|
+
// child CLI invocation's cwd at the inner workspace's location), and
|
|
7
|
+
// every one of them needs the monorepo root to discover sibling
|
|
8
|
+
// workspaces, resolve `workspace:^` deps, and walk the dep graph.
|
|
9
|
+
//
|
|
10
|
+
// Sanity-checked via `discoverWorkspaces(candidate)`: the candidate
|
|
11
|
+
// monorepo must actually contain `start` as one of its workspaces.
|
|
12
|
+
// Without this guard, a grand-parent monorepo unrelated to `start`
|
|
13
|
+
// could be picked up.
|
|
14
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
import { discoverWorkspaces } from '@gjsify/workspace';
|
|
17
|
+
function readPackageJson(path) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function findWorkspaceRoot(start) {
|
|
26
|
+
let dir = start;
|
|
27
|
+
for (let i = 0; i < 12; i++) {
|
|
28
|
+
const pkgPath = join(dir, 'package.json');
|
|
29
|
+
if (existsSync(pkgPath)) {
|
|
30
|
+
const pkg = readPackageJson(pkgPath);
|
|
31
|
+
if (pkg?.workspaces !== undefined) {
|
|
32
|
+
try {
|
|
33
|
+
const ws = discoverWorkspaces(dir);
|
|
34
|
+
if (dir === start || ws.some((w) => w.location === start))
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
37
|
+
catch { /* not a usable workspace root */ }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const parent = resolve(dir, '..');
|
|
41
|
+
if (parent === dir)
|
|
42
|
+
break;
|
|
43
|
+
dir = parent;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,69 +1,71 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"bin": {
|
|
10
|
-
"gjsify": "./lib/index.js"
|
|
11
|
-
},
|
|
12
|
-
"gjsify": {
|
|
2
|
+
"name": "@gjsify/cli",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "CLI for Gjsify",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"module": "lib/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
13
9
|
"bin": {
|
|
14
|
-
|
|
10
|
+
"gjsify": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"gjsify": {
|
|
13
|
+
"bin": {
|
|
14
|
+
"gjsify": "./dist/cli.gjs.mjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"dist/cli.gjs.mjs",
|
|
20
|
+
"showcases.json"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clear": "rm -rf lib dist tsconfig.tsbuildinfo || exit 0",
|
|
24
|
+
"check": "tsc --noEmit",
|
|
25
|
+
"start": "node lib/index.js",
|
|
26
|
+
"build": "tsc && gjsify run chmod",
|
|
27
|
+
"build:gjs-bundle": "node lib/index.js build src/index.ts --app gjs --outfile dist/cli.gjs.mjs",
|
|
28
|
+
"chmod": "chmod +x ./lib/index.js",
|
|
29
|
+
"build:test:node": "node lib/index.js build src/test.mts --app node --outfile dist/test.node.mjs",
|
|
30
|
+
"test:node": "node dist/test.node.mjs",
|
|
31
|
+
"test": "gjsify run build:test:node && gjsify run test:node"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"gjs",
|
|
35
|
+
"node",
|
|
36
|
+
"gjsify",
|
|
37
|
+
"cli"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@gjsify/buffer": "workspace:^",
|
|
41
|
+
"@gjsify/create-app": "workspace:^",
|
|
42
|
+
"@gjsify/node-globals": "workspace:^",
|
|
43
|
+
"@gjsify/node-polyfills": "workspace:^",
|
|
44
|
+
"@gjsify/npm-registry": "workspace:^",
|
|
45
|
+
"@gjsify/resolve-npm": "workspace:^",
|
|
46
|
+
"@gjsify/rolldown-plugin-gjsify": "workspace:^",
|
|
47
|
+
"@gjsify/rolldown-plugin-pnp": "workspace:^",
|
|
48
|
+
"@gjsify/semver": "workspace:^",
|
|
49
|
+
"@gjsify/tar": "workspace:^",
|
|
50
|
+
"@gjsify/web-polyfills": "workspace:^",
|
|
51
|
+
"@gjsify/workspace": "workspace:^",
|
|
52
|
+
"cosmiconfig": "^9.0.1",
|
|
53
|
+
"get-tsconfig": "^4.14.0",
|
|
54
|
+
"pkg-types": "^2.3.1",
|
|
55
|
+
"rolldown": "^1.0.0",
|
|
56
|
+
"yargs": "^18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@gjsify/unit": "workspace:^",
|
|
60
|
+
"@types/yargs": "^17.0.35",
|
|
61
|
+
"typescript": "^6.0.3"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@gjsify/rolldown-native": "workspace:^"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@gjsify/rolldown-native": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
15
70
|
}
|
|
16
|
-
|
|
17
|
-
"files": [
|
|
18
|
-
"lib",
|
|
19
|
-
"dist/cli.gjs.mjs",
|
|
20
|
-
"showcases.json"
|
|
21
|
-
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"clear": "rm -rf lib dist tsconfig.tsbuildinfo || exit 0",
|
|
24
|
-
"check": "tsc --noEmit",
|
|
25
|
-
"start": "node lib/index.js",
|
|
26
|
-
"build": "tsc && yarn chmod",
|
|
27
|
-
"build:gjs-bundle": "node lib/index.js build src/index.ts --app gjs --outfile dist/cli.gjs.mjs",
|
|
28
|
-
"chmod": "chmod +x ./lib/index.js",
|
|
29
|
-
"build:test:node": "node lib/index.js build src/test.mts --app node --outfile dist/test.node.mjs",
|
|
30
|
-
"test:node": "node dist/test.node.mjs",
|
|
31
|
-
"test": "yarn build:test:node && yarn test:node"
|
|
32
|
-
},
|
|
33
|
-
"keywords": [
|
|
34
|
-
"gjs",
|
|
35
|
-
"node",
|
|
36
|
-
"gjsify",
|
|
37
|
-
"cli"
|
|
38
|
-
],
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"@gjsify/create-app": "^0.4.0",
|
|
41
|
-
"@gjsify/node-polyfills": "^0.4.0",
|
|
42
|
-
"@gjsify/npm-registry": "^0.4.0",
|
|
43
|
-
"@gjsify/resolve-npm": "^0.4.0",
|
|
44
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.4.0",
|
|
45
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.0",
|
|
46
|
-
"@gjsify/semver": "^0.4.0",
|
|
47
|
-
"@gjsify/tar": "^0.4.0",
|
|
48
|
-
"@gjsify/web-polyfills": "^0.4.0",
|
|
49
|
-
"@gjsify/workspace": "^0.4.0",
|
|
50
|
-
"cosmiconfig": "^9.0.1",
|
|
51
|
-
"get-tsconfig": "^4.14.0",
|
|
52
|
-
"pkg-types": "^2.3.1",
|
|
53
|
-
"rolldown": "^1.0.0",
|
|
54
|
-
"yargs": "^18.0.0"
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"@gjsify/unit": "^0.4.0",
|
|
58
|
-
"@types/yargs": "^17.0.35",
|
|
59
|
-
"typescript": "^6.0.3"
|
|
60
|
-
},
|
|
61
|
-
"peerDependencies": {
|
|
62
|
-
"@gjsify/rolldown-native": "^0.4.0"
|
|
63
|
-
},
|
|
64
|
-
"peerDependenciesMeta": {
|
|
65
|
-
"@gjsify/rolldown-native": {
|
|
66
|
-
"optional": true
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
71
|
+
}
|