@gjsify/cli 0.4.0 → 0.4.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.
@@ -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
- // Out of scope (deferred to Phase 4): lockfile, peerDependencies validation,
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 = 1;
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
- const byName = new Map(nodes.map((n) => [n.name, n]));
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
- const resolved = new Map();
101
- const queue = specs.map(parseSpec);
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 spec = queue.shift();
104
- if (resolved.has(spec.name)) {
105
- // Single-version-per-name policy (npm v6 semantics). Phase 4 v2
106
- // (when peer-dep validation lands) revisits this for duplication.
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
- const packument = await fetchPkg(spec.name);
110
- const version = pickVersion(packument, spec.range);
111
- if (!version) {
112
- throw new Error(`No version of ${spec.name} satisfies ${spec.range}`);
113
- }
114
- const v = packument.versions[version];
115
- if (!v) {
116
- throw new Error(`Packument for ${spec.name} promised ${version} but no entry exists`);
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
- const node = {
119
- name: spec.name,
120
- version,
121
- tarballUrl: v.dist.tarball,
122
- integrity: v.dist.integrity,
123
- dependencies: v.dependencies ?? {},
124
- optionalDependencies: v.optionalDependencies ?? {},
125
- bin: v.bin,
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
- for (const [depName, depRange] of Object.entries(node.optionalDependencies)) {
134
- if (!resolved.has(depName))
135
- queue.push({ name: depName, range: depRange });
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
- return Array.from(resolved.values());
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 output (diff-friendly).
158
- const sorted = [...nodes].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
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.name] = {
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(([name, entry]) => ({
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
- const queue = [...nodes];
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(worker());
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
- async function worker() {
266
- while (queue.length > 0) {
267
- const node = queue.shift();
268
- if (!node)
269
- return;
270
- const dest = path.join(prefix, "node_modules", node.name);
271
- log("fetch: %s@%s ← %s", node.name, node.version, node.tarballUrl);
272
- const bytes = await fetchTarball(node.tarballUrl, {
273
- npmrc,
274
- integrity: node.integrity,
275
- });
276
- fs.rmSync(dest, { recursive: true, force: true });
277
- fs.mkdirSync(dest, { recursive: true });
278
- await extractTarball(bytes, dest);
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, "node_modules", node.name, binTarget);
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
- "name": "@gjsify/cli",
3
- "version": "0.4.0",
4
- "description": "CLI for Gjsify",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "module": "lib/index.js",
8
- "types": "lib/index.d.ts",
9
- "bin": {
10
- "gjsify": "./lib/index.js"
11
- },
12
- "gjsify": {
2
+ "name": "@gjsify/cli",
3
+ "version": "0.4.4",
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
- "gjsify": "./dist/cli.gjs.mjs"
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": "^0.4.4",
41
+ "@gjsify/create-app": "^0.4.4",
42
+ "@gjsify/node-globals": "^0.4.4",
43
+ "@gjsify/node-polyfills": "^0.4.4",
44
+ "@gjsify/npm-registry": "^0.4.4",
45
+ "@gjsify/resolve-npm": "^0.4.4",
46
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.4",
47
+ "@gjsify/rolldown-plugin-pnp": "^0.4.4",
48
+ "@gjsify/semver": "^0.4.4",
49
+ "@gjsify/tar": "^0.4.4",
50
+ "@gjsify/web-polyfills": "^0.4.4",
51
+ "@gjsify/workspace": "^0.4.4",
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": "^0.4.4",
60
+ "@types/yargs": "^17.0.35",
61
+ "typescript": "^6.0.3"
62
+ },
63
+ "peerDependencies": {
64
+ "@gjsify/rolldown-native": "^0.4.4"
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
+ }