@gjsify/cli 0.4.27 → 0.4.29
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 +34 -32
- package/lib/actions/barrels-generate.js +1 -5
- package/lib/actions/build.d.ts +3 -3
- package/lib/actions/build.js +56 -64
- package/lib/bundler-pick.d.ts +3 -3
- package/lib/bundler-pick.js +5 -6
- package/lib/commands/build.js +37 -31
- package/lib/commands/check.js +3 -3
- package/lib/commands/fix.js +33 -23
- package/lib/commands/flatpak/build.js +6 -2
- package/lib/commands/flatpak/check.js +9 -3
- package/lib/commands/flatpak/ci.js +1 -2
- package/lib/commands/flatpak/deps.js +1 -2
- package/lib/commands/flatpak/diff.js +2 -6
- package/lib/commands/flatpak/init.js +19 -19
- package/lib/commands/flatpak/release.js +2 -2
- package/lib/commands/flatpak/scaffold.js +3 -11
- package/lib/commands/flatpak/sync-flathub.js +4 -8
- package/lib/commands/flatpak/utils.js +1 -6
- package/lib/commands/foreach.js +5 -14
- package/lib/commands/format.js +54 -41
- package/lib/commands/gettext.js +2 -10
- package/lib/commands/gresource.js +2 -8
- package/lib/commands/gsettings.js +1 -3
- package/lib/commands/install.js +13 -6
- package/lib/commands/lint.d.ts +1 -1
- package/lib/commands/lint.js +22 -22
- package/lib/commands/pack.js +29 -17
- package/lib/commands/publish.d.ts +1 -0
- package/lib/commands/publish.js +113 -21
- package/lib/commands/run.js +2 -6
- package/lib/commands/self-update.js +36 -8
- package/lib/commands/showcase.js +1 -1
- package/lib/commands/system-check.js +8 -11
- package/lib/commands/test.js +12 -8
- package/lib/commands/uninstall.js +1 -3
- package/lib/commands/upgrade.d.ts +1 -1
- package/lib/commands/upgrade.js +109 -120
- package/lib/commands/workspace.js +1 -3
- package/lib/config.js +18 -13
- package/lib/index.js +21 -0
- package/lib/templates/install.mjs.tmpl +20 -14
- package/lib/templates/oxfmtrc.tmpl +54 -0
- package/lib/templates/oxlintrc.json.tmpl +35 -0
- package/lib/types/config-data.d.ts +23 -13
- package/lib/utils/check-system-deps.js +10 -4
- package/lib/utils/detect-native-packages.js +1 -1
- package/lib/utils/dlx-cache.js +2 -7
- package/lib/utils/install-backend-native.d.ts +2 -2
- package/lib/utils/install-backend-native.js +72 -63
- package/lib/utils/install-backend.d.ts +13 -0
- package/lib/utils/install-backend.js +2 -1
- package/lib/utils/install-global.js +1 -3
- package/lib/utils/normalize-bundler-options.js +52 -17
- package/lib/utils/oxc-resolve.d.ts +63 -0
- package/lib/utils/oxc-resolve.js +264 -0
- package/lib/utils/pkg-json-edit.js +1 -6
- package/lib/utils/run-gjs.js +1 -4
- package/lib/utils/run-lifecycle-script.js +3 -7
- package/lib/utils/workspace-root.js +3 -1
- package/package.json +17 -17
- package/lib/templates/biome.json.tmpl +0 -79
- package/lib/utils/biome-resolve.d.ts +0 -47
- package/lib/utils/biome-resolve.js +0 -204
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
+
"useTabs": false,
|
|
4
|
+
"tabWidth": 4,
|
|
5
|
+
"printWidth": 120,
|
|
6
|
+
"singleQuote": true,
|
|
7
|
+
"jsxSingleQuote": false,
|
|
8
|
+
"quoteProps": "as-needed",
|
|
9
|
+
"trailingComma": "all",
|
|
10
|
+
"semi": true,
|
|
11
|
+
"arrowParens": "always",
|
|
12
|
+
"bracketSameLine": false,
|
|
13
|
+
"bracketSpacing": true,
|
|
14
|
+
"ignorePatterns": [
|
|
15
|
+
"**/*.json",
|
|
16
|
+
"**/*.jsonc",
|
|
17
|
+
"**/*.json5",
|
|
18
|
+
"**/*.css",
|
|
19
|
+
"**/*.scss",
|
|
20
|
+
"**/*.sass",
|
|
21
|
+
"**/*.less",
|
|
22
|
+
"**/*.html",
|
|
23
|
+
"**/*.htm",
|
|
24
|
+
"**/*.md",
|
|
25
|
+
"**/*.mdx",
|
|
26
|
+
"**/*.yml",
|
|
27
|
+
"**/*.yaml",
|
|
28
|
+
"**/*.vue",
|
|
29
|
+
"**/*.svelte",
|
|
30
|
+
"**/*.toml",
|
|
31
|
+
"**/*.graphql",
|
|
32
|
+
"**/*.gql",
|
|
33
|
+
"**/node_modules",
|
|
34
|
+
"**/dist",
|
|
35
|
+
"**/lib",
|
|
36
|
+
"**/build",
|
|
37
|
+
"**/build-dir",
|
|
38
|
+
"**/builddir",
|
|
39
|
+
"**/flatpak-build",
|
|
40
|
+
"**/.flatpak-builder",
|
|
41
|
+
"**/repo",
|
|
42
|
+
"**/coverage",
|
|
43
|
+
"**/refs",
|
|
44
|
+
"**/@types",
|
|
45
|
+
"**/templates",
|
|
46
|
+
"**/prebuilds",
|
|
47
|
+
"**/cli.gjs.mjs",
|
|
48
|
+
"**/test.gjs.mjs",
|
|
49
|
+
"**/test.node.mjs",
|
|
50
|
+
"**/*.gresource",
|
|
51
|
+
"**/*.compiled",
|
|
52
|
+
"**/*.metainfo.xml"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"categories": {
|
|
4
|
+
"correctness": "error"
|
|
5
|
+
},
|
|
6
|
+
"rules": {
|
|
7
|
+
"typescript/consistent-type-imports": "warn",
|
|
8
|
+
"typescript/no-explicit-any": "warn",
|
|
9
|
+
"typescript/no-non-null-assertion": "off",
|
|
10
|
+
"unicorn/prefer-node-protocol": "error",
|
|
11
|
+
"eslint/no-unused-vars": "warn"
|
|
12
|
+
},
|
|
13
|
+
"ignorePatterns": [
|
|
14
|
+
"**/node_modules",
|
|
15
|
+
"**/dist",
|
|
16
|
+
"**/lib",
|
|
17
|
+
"**/build",
|
|
18
|
+
"**/build-dir",
|
|
19
|
+
"**/builddir",
|
|
20
|
+
"**/flatpak-build",
|
|
21
|
+
"**/.flatpak-builder",
|
|
22
|
+
"**/repo",
|
|
23
|
+
"**/coverage",
|
|
24
|
+
"**/refs",
|
|
25
|
+
"**/@types",
|
|
26
|
+
"**/templates",
|
|
27
|
+
"**/prebuilds",
|
|
28
|
+
"**/cli.gjs.mjs",
|
|
29
|
+
"**/test.gjs.mjs",
|
|
30
|
+
"**/test.node.mjs",
|
|
31
|
+
"**/*.gresource",
|
|
32
|
+
"**/*.compiled",
|
|
33
|
+
"**/*.metainfo.xml"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -165,20 +165,30 @@ export interface ConfigData {
|
|
|
165
165
|
}>;
|
|
166
166
|
/**
|
|
167
167
|
* Extension → loader-kind map for files Rolldown does not classify
|
|
168
|
-
* natively.
|
|
169
|
-
*
|
|
170
|
-
*
|
|
168
|
+
* natively. Replaces the legacy esbuild `loader: { '.ui': 'text', '.png': 'dataurl' }`
|
|
169
|
+
* pattern.
|
|
170
|
+
*
|
|
171
|
+
* Loader kinds:
|
|
172
|
+
* `'text'` — file contents as a JS string default export
|
|
173
|
+
* (`export default "<source>"`). Good for GLSL shaders,
|
|
174
|
+
* `.ui` GtkBuilder XML, `.asm`, etc.
|
|
175
|
+
* `'dataurl'` — `data:<mime>;base64,<b64>` string default export.
|
|
176
|
+
* MIME is inferred from the extension (.png → image/png,
|
|
177
|
+
* .jpg → image/jpeg, .gif → image/gif, .svg → image/svg+xml,
|
|
178
|
+
* .webp → image/webp, fallback application/octet-stream).
|
|
179
|
+
* Good for Excalibur's `ImageSource` and any library that
|
|
180
|
+
* accepts a data: URL rather than a separate asset file.
|
|
171
181
|
*
|
|
172
182
|
* Example:
|
|
173
183
|
* ```jsonc
|
|
174
|
-
* "loaders": { ".ui": "text", ".
|
|
184
|
+
* "loaders": { ".ui": "text", ".glsl": "text", ".png": "dataurl" }
|
|
175
185
|
* ```
|
|
176
186
|
*
|
|
177
187
|
* Lives at the top level (not under `bundler`) so it doesn't leak into
|
|
178
|
-
* Rolldown's options on pass-through; the CLI converts it into a
|
|
179
|
-
*
|
|
188
|
+
* Rolldown's options on pass-through; the CLI converts it into a plugin
|
|
189
|
+
* prepended to the bundler's plugin chain.
|
|
180
190
|
*/
|
|
181
|
-
loaders?: Record<string, 'text'>;
|
|
191
|
+
loaders?: Record<string, 'text' | 'dataurl'>;
|
|
182
192
|
/**
|
|
183
193
|
* Flatpak-related configuration consumed by `gjsify flatpak <sub>`.
|
|
184
194
|
* Lives in its own top-level namespace so the bundler config doesn't
|
|
@@ -188,8 +198,8 @@ export interface ConfigData {
|
|
|
188
198
|
flatpak?: ConfigDataFlatpak;
|
|
189
199
|
/**
|
|
190
200
|
* Format/lint config consumed by `gjsify format` / `gjsify lint` /
|
|
191
|
-
* `gjsify fix`. Thin shell —
|
|
192
|
-
* configuration
|
|
201
|
+
* `gjsify fix`. Thin shell — oxc's own `.oxfmtrc.json` / `.oxlintrc.json`
|
|
202
|
+
* are the real configuration files; we only need a pointer here.
|
|
193
203
|
*/
|
|
194
204
|
format?: ConfigDataFormat;
|
|
195
205
|
/**
|
|
@@ -198,12 +208,12 @@ export interface ConfigData {
|
|
|
198
208
|
*/
|
|
199
209
|
test?: ConfigDataTest;
|
|
200
210
|
}
|
|
201
|
-
/** Optional pointer to a non-default
|
|
211
|
+
/** Optional pointer to a non-default oxc config file. */
|
|
202
212
|
export interface ConfigDataFormat {
|
|
203
213
|
/**
|
|
204
|
-
* Path to
|
|
205
|
-
* falls back to the recommended
|
|
206
|
-
* (writable via `gjsify format --init`).
|
|
214
|
+
* Path to an `.oxfmtrc.json` / `.oxlintrc.json`. Default: walks up from
|
|
215
|
+
* cwd to find one; falls back to the recommended templates shipped with
|
|
216
|
+
* `gjsify` (writable via `gjsify format --init`).
|
|
207
217
|
*/
|
|
208
218
|
configPath?: string;
|
|
209
219
|
}
|
|
@@ -29,7 +29,7 @@ function checkBinary(id, name, binary, versionArgs, severity, parseVersion, requ
|
|
|
29
29
|
const out = tryExecFile(binary, versionArgs);
|
|
30
30
|
if (out === null)
|
|
31
31
|
return { id, name, found: false, severity, requiredBy };
|
|
32
|
-
const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
|
|
32
|
+
const version = parseVersion ? parseVersion(out) : (out.split('\n')[0] ?? out);
|
|
33
33
|
return { id, name, found: true, version, severity, requiredBy };
|
|
34
34
|
}
|
|
35
35
|
/** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
|
|
@@ -51,7 +51,9 @@ function checkNpmPackage(id, name, packageName, cwd, severity, requiredBy) {
|
|
|
51
51
|
requireFromCwd.resolve(packageName);
|
|
52
52
|
return { id, name, found: true, severity, requiredBy };
|
|
53
53
|
}
|
|
54
|
-
catch {
|
|
54
|
+
catch {
|
|
55
|
+
/* not in project, try CLI fallback */
|
|
56
|
+
}
|
|
55
57
|
// 2. Fallback: CLI's own node_modules
|
|
56
58
|
try {
|
|
57
59
|
const requireFromCli = createRequire(import.meta.url);
|
|
@@ -156,7 +158,9 @@ function discoverGjsifyPackages(cwd) {
|
|
|
156
158
|
try {
|
|
157
159
|
topPkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
158
160
|
}
|
|
159
|
-
catch {
|
|
161
|
+
catch {
|
|
162
|
+
/* ignore */
|
|
163
|
+
}
|
|
160
164
|
const directDeps = {
|
|
161
165
|
...topPkg.dependencies,
|
|
162
166
|
...topPkg.devDependencies,
|
|
@@ -169,7 +173,9 @@ function discoverGjsifyPackages(cwd) {
|
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
}
|
|
172
|
-
catch {
|
|
176
|
+
catch {
|
|
177
|
+
/* ignore */
|
|
178
|
+
}
|
|
173
179
|
// Also include direct deps even if node_modules walk failed
|
|
174
180
|
for (const dep of Object.keys(directDeps)) {
|
|
175
181
|
if (dep.startsWith('@gjsify/'))
|
|
@@ -131,7 +131,7 @@ export function detectNativePackages(startDir) {
|
|
|
131
131
|
* Prepends the new paths to any existing values from the environment.
|
|
132
132
|
*/
|
|
133
133
|
export function buildNativeEnv(packages) {
|
|
134
|
-
const dirs = packages.map(p => p.prebuildsDir);
|
|
134
|
+
const dirs = packages.map((p) => p.prebuildsDir);
|
|
135
135
|
const existing_ld = process.env['LD_LIBRARY_PATH'] ?? '';
|
|
136
136
|
const existing_gi = process.env['GI_TYPELIB_PATH'] ?? '';
|
|
137
137
|
const LD_LIBRARY_PATH = [...dirs, ...(existing_ld ? [existing_ld] : [])].join(':');
|
package/lib/utils/dlx-cache.js
CHANGED
|
@@ -84,13 +84,8 @@ export function symlinkSwap(cacheDir, prepareDir) {
|
|
|
84
84
|
const linkPath = join(cacheDir, 'pkg');
|
|
85
85
|
const tmpName = `pkg.tmp-${Date.now().toString(16)}-${process.pid.toString(16)}`;
|
|
86
86
|
const tmpLink = join(cacheDir, tmpName);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
// If we cannot even create the tmp link, give up.
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
87
|
+
// If we cannot even create the tmp link, give up (the error propagates).
|
|
88
|
+
symlinkSync(prepareDir, tmpLink, 'dir');
|
|
94
89
|
try {
|
|
95
90
|
renameSync(tmpLink, linkPath);
|
|
96
91
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Packument } from
|
|
2
|
-
import type { InstallOptions } from
|
|
1
|
+
import { type Packument } from '@gjsify/npm-registry';
|
|
2
|
+
import type { InstallOptions } from './install-backend.ts';
|
|
3
3
|
interface ParsedSpec {
|
|
4
4
|
name: string;
|
|
5
5
|
range: string;
|
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Out of scope (still deferred): peerDependencies validation,
|
|
15
15
|
// lifecycle scripts, git/file specs.
|
|
16
|
-
import * as fs from
|
|
17
|
-
import * as path from
|
|
18
|
-
import * as os from
|
|
19
|
-
import { Range, SemVer, maxSatisfying, satisfies
|
|
20
|
-
import { DEFAULT_REGISTRY, fetchPackument, fetchTarball, parseNpmrc, } from
|
|
21
|
-
import { extractTarball } from
|
|
22
|
-
const DEFAULT_CONCURRENCY = Number(process.env.GJSIFY_INSTALL_CONCURRENCY ??
|
|
23
|
-
const LOCKFILE_NAME =
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import * as os from 'node:os';
|
|
19
|
+
import { Range, SemVer, maxSatisfying, satisfies } from '@gjsify/semver';
|
|
20
|
+
import { DEFAULT_REGISTRY, fetchPackument, fetchTarball, parseNpmrc, } from '@gjsify/npm-registry';
|
|
21
|
+
import { extractTarball } from '@gjsify/tar';
|
|
22
|
+
const DEFAULT_CONCURRENCY = Number(process.env.GJSIFY_INSTALL_CONCURRENCY ?? '8') || 8;
|
|
23
|
+
const LOCKFILE_NAME = 'gjsify-lock.json';
|
|
24
24
|
const LOCKFILE_VERSION = 2;
|
|
25
25
|
export async function installPackagesNative(opts) {
|
|
26
26
|
if (opts.specs.length === 0) {
|
|
27
|
-
throw new Error(
|
|
27
|
+
throw new Error('installPackagesNative: empty specs list');
|
|
28
28
|
}
|
|
29
29
|
fs.mkdirSync(opts.prefix, { recursive: true });
|
|
30
30
|
const npmrc = await loadNpmrc(opts);
|
|
@@ -47,25 +47,25 @@ export async function installPackagesNative(opts) {
|
|
|
47
47
|
throw new Error(`install: --immutable but ${lockfilePath} is stale.\n${drift}\n` +
|
|
48
48
|
`Re-run \`gjsify install\` (without --immutable) to refresh the lockfile.`);
|
|
49
49
|
}
|
|
50
|
-
log(
|
|
50
|
+
log('install: --immutable, using lockfile (%d package(s))', Object.keys(existingLock.packages).length);
|
|
51
51
|
nodes = lockfileToNodes(existingLock);
|
|
52
52
|
}
|
|
53
53
|
else if (existingLock && lockfileMatchesRequest(existingLock, opts.specs)) {
|
|
54
|
-
log(
|
|
54
|
+
log('install: using lockfile (%d package(s))', Object.keys(existingLock.packages).length);
|
|
55
55
|
nodes = lockfileToNodes(existingLock);
|
|
56
56
|
}
|
|
57
57
|
else {
|
|
58
|
-
log(
|
|
59
|
-
nodes = await resolveDeps(opts.specs, npmrc, log, opts.overrides);
|
|
58
|
+
log('install: resolving %d top-level spec(s) → %s', opts.specs.length, opts.prefix);
|
|
59
|
+
nodes = await resolveDeps(opts.specs, npmrc, log, opts.overrides, opts.skipDeps);
|
|
60
60
|
if (opts.lockfile) {
|
|
61
61
|
writeLockfile(lockfilePath, opts.specs, nodes);
|
|
62
|
-
log(
|
|
62
|
+
log('install: wrote %s (%d entries)', LOCKFILE_NAME, nodes.length);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
log(
|
|
65
|
+
log('install: downloading %d tarball(s)', nodes.length);
|
|
66
66
|
await downloadAndExtractAll(nodes, opts.prefix, npmrc, log);
|
|
67
67
|
await linkBins(nodes, opts.prefix, log);
|
|
68
|
-
log(
|
|
68
|
+
log('install: done');
|
|
69
69
|
// Surface the top-level requested packages so callers can update
|
|
70
70
|
// package.json with the resolved version (mirrors `npm install --save`
|
|
71
71
|
// behavior). Sub-deps are not included.
|
|
@@ -94,14 +94,14 @@ function topLevelResolutions(specs, nodes) {
|
|
|
94
94
|
return out;
|
|
95
95
|
}
|
|
96
96
|
function parseSpecName(spec) {
|
|
97
|
-
if (spec.startsWith(
|
|
98
|
-
const slash = spec.indexOf(
|
|
97
|
+
if (spec.startsWith('@')) {
|
|
98
|
+
const slash = spec.indexOf('/');
|
|
99
99
|
if (slash === -1)
|
|
100
100
|
return spec;
|
|
101
|
-
const at = spec.indexOf(
|
|
101
|
+
const at = spec.indexOf('@', slash + 1);
|
|
102
102
|
return at === -1 ? spec : spec.slice(0, at);
|
|
103
103
|
}
|
|
104
|
-
const at = spec.indexOf(
|
|
104
|
+
const at = spec.indexOf('@');
|
|
105
105
|
return at === -1 ? spec : spec.slice(0, at);
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
@@ -119,7 +119,7 @@ function parseSpecName(spec) {
|
|
|
119
119
|
* the root. Each placement returns a `ResolvedNode` whose `installPath`
|
|
120
120
|
* captures where it lives in the tree.
|
|
121
121
|
*/
|
|
122
|
-
async function resolveDeps(specs, npmrc, log, overrides) {
|
|
122
|
+
async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
|
|
123
123
|
const applyOverride = (name, range) => {
|
|
124
124
|
if (!overrides)
|
|
125
125
|
return range;
|
|
@@ -128,7 +128,7 @@ async function resolveDeps(specs, npmrc, log, overrides) {
|
|
|
128
128
|
return range;
|
|
129
129
|
if (override === range)
|
|
130
130
|
return range;
|
|
131
|
-
log(
|
|
131
|
+
log('install: override %s %s → %s', name, range, override);
|
|
132
132
|
return override;
|
|
133
133
|
};
|
|
134
134
|
const packumentCache = new Map();
|
|
@@ -139,7 +139,7 @@ async function resolveDeps(specs, npmrc, log, overrides) {
|
|
|
139
139
|
const fresh = fetchPackument(name, {
|
|
140
140
|
npmrc,
|
|
141
141
|
onRetry: ({ attempt, error, delayMs }) => {
|
|
142
|
-
log(
|
|
142
|
+
log('packument %s: retry %d after %dms (%s)', name, attempt, delayMs, errMsg(error));
|
|
143
143
|
},
|
|
144
144
|
});
|
|
145
145
|
packumentCache.set(name, fresh);
|
|
@@ -200,19 +200,31 @@ async function resolveDeps(specs, npmrc, log, overrides) {
|
|
|
200
200
|
if (installPath === `node_modules/${edge.name}`) {
|
|
201
201
|
root.set(edge.name, node);
|
|
202
202
|
}
|
|
203
|
-
log(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
log('resolve: %s@%s ← %s (at %s)', edge.name, version, edge.range, installPath);
|
|
204
|
+
if (!skipDeps) {
|
|
205
|
+
for (const [depName, depRange] of Object.entries(node.dependencies)) {
|
|
206
|
+
queue.push({
|
|
207
|
+
from: installPath,
|
|
208
|
+
name: depName,
|
|
209
|
+
range: applyOverride(depName, depRange),
|
|
210
|
+
required: true,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
for (const [depName, depRange] of Object.entries(node.optionalDependencies)) {
|
|
214
|
+
queue.push({
|
|
215
|
+
from: installPath,
|
|
216
|
+
name: depName,
|
|
217
|
+
range: applyOverride(depName, depRange),
|
|
218
|
+
required: false,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
209
221
|
}
|
|
210
222
|
}
|
|
211
223
|
catch (e) {
|
|
212
224
|
// Optional deps that fail to resolve are skipped — yarn/npm
|
|
213
225
|
// behavior. Required deps re-throw.
|
|
214
226
|
if (!edge.required) {
|
|
215
|
-
log(
|
|
227
|
+
log('resolve: optional dep %s@%s skipped (%s)', edge.name, edge.range, e.message);
|
|
216
228
|
continue;
|
|
217
229
|
}
|
|
218
230
|
throw e;
|
|
@@ -243,12 +255,12 @@ function findVisible(requesterPath, name, byPath) {
|
|
|
243
255
|
// eslint-disable-next-line no-constant-condition
|
|
244
256
|
while (true) {
|
|
245
257
|
// Find the deepest `/node_modules/<pkg>` in `p`, strip it.
|
|
246
|
-
const idx = p.lastIndexOf(
|
|
258
|
+
const idx = p.lastIndexOf('/node_modules/');
|
|
247
259
|
if (idx < 0)
|
|
248
260
|
break;
|
|
249
261
|
p = p.slice(0, idx);
|
|
250
262
|
candidates.push(`${p}/node_modules/${name}`);
|
|
251
|
-
if (p ===
|
|
263
|
+
if (p === '')
|
|
252
264
|
break;
|
|
253
265
|
}
|
|
254
266
|
}
|
|
@@ -299,10 +311,10 @@ function readLockfile(lockfilePath) {
|
|
|
299
311
|
if (!fs.existsSync(lockfilePath))
|
|
300
312
|
return null;
|
|
301
313
|
try {
|
|
302
|
-
const parsed = JSON.parse(fs.readFileSync(lockfilePath,
|
|
314
|
+
const parsed = JSON.parse(fs.readFileSync(lockfilePath, 'utf-8'));
|
|
303
315
|
if (parsed.lockfileVersion !== LOCKFILE_VERSION)
|
|
304
316
|
return null;
|
|
305
|
-
if (!parsed.packages || typeof parsed.packages !==
|
|
317
|
+
if (!parsed.packages || typeof parsed.packages !== 'object')
|
|
306
318
|
return null;
|
|
307
319
|
return parsed;
|
|
308
320
|
}
|
|
@@ -328,7 +340,7 @@ function writeLockfile(lockfilePath, specs, nodes) {
|
|
|
328
340
|
requested: [...specs],
|
|
329
341
|
packages,
|
|
330
342
|
};
|
|
331
|
-
fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) +
|
|
343
|
+
fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) + '\n');
|
|
332
344
|
}
|
|
333
345
|
function lockfileToNodes(lockfile) {
|
|
334
346
|
return Object.entries(lockfile.packages).map(([installPath, entry]) => ({
|
|
@@ -347,8 +359,8 @@ function lockfileToNodes(lockfile) {
|
|
|
347
359
|
function nameFromInstallPath(installPath) {
|
|
348
360
|
// Last `node_modules/` boundary, then the rest is the package name
|
|
349
361
|
// (single segment unscoped, or `@scope/pkg` scoped).
|
|
350
|
-
const idx = installPath.lastIndexOf(
|
|
351
|
-
const after = idx < 0 ? installPath.replace(/^node_modules\//,
|
|
362
|
+
const idx = installPath.lastIndexOf('/node_modules/');
|
|
363
|
+
const after = idx < 0 ? installPath.replace(/^node_modules\//, '') : installPath.slice(idx + '/node_modules/'.length);
|
|
352
364
|
return after;
|
|
353
365
|
}
|
|
354
366
|
function lockfileMatchesRequest(lockfile, specs) {
|
|
@@ -379,10 +391,10 @@ function describeLockfileDrift(lockfile, specs) {
|
|
|
379
391
|
return null;
|
|
380
392
|
const lines = [];
|
|
381
393
|
if (added.length > 0)
|
|
382
|
-
lines.push(` + ${added.sort().join(
|
|
394
|
+
lines.push(` + ${added.sort().join('\n + ')}`);
|
|
383
395
|
if (removed.length > 0)
|
|
384
|
-
lines.push(` - ${removed.sort().join(
|
|
385
|
-
return lines.join(
|
|
396
|
+
lines.push(` - ${removed.sort().join('\n - ')}`);
|
|
397
|
+
return lines.join('\n');
|
|
386
398
|
}
|
|
387
399
|
// Exported for unit-testing — keep the function name + signature
|
|
388
400
|
// stable, the install-backend itself still calls it via the local
|
|
@@ -398,25 +410,25 @@ export function parseSpec(raw) {
|
|
|
398
410
|
// shipped only prereleases (4.0.0-rc.17 is the `latest` tag, no
|
|
399
411
|
// stable 4.x yet) and `*` was selecting the abandoned 3.3.0
|
|
400
412
|
// instead.
|
|
401
|
-
if (raw.startsWith(
|
|
402
|
-
const slash = raw.indexOf(
|
|
413
|
+
if (raw.startsWith('@')) {
|
|
414
|
+
const slash = raw.indexOf('/');
|
|
403
415
|
if (slash < 0)
|
|
404
416
|
throw new Error(`Invalid spec (scoped name without slash): ${raw}`);
|
|
405
|
-
const at = raw.indexOf(
|
|
417
|
+
const at = raw.indexOf('@', slash);
|
|
406
418
|
if (at < 0)
|
|
407
|
-
return { name: raw, range:
|
|
408
|
-
return { name: raw.slice(0, at), range: raw.slice(at + 1) ||
|
|
419
|
+
return { name: raw, range: 'latest' };
|
|
420
|
+
return { name: raw.slice(0, at), range: raw.slice(at + 1) || 'latest' };
|
|
409
421
|
}
|
|
410
|
-
const at = raw.indexOf(
|
|
422
|
+
const at = raw.indexOf('@');
|
|
411
423
|
if (at < 0)
|
|
412
|
-
return { name: raw, range:
|
|
413
|
-
return { name: raw.slice(0, at), range: raw.slice(at + 1) ||
|
|
424
|
+
return { name: raw, range: 'latest' };
|
|
425
|
+
return { name: raw.slice(0, at), range: raw.slice(at + 1) || 'latest' };
|
|
414
426
|
}
|
|
415
427
|
// Exported for unit-testing. Internal API.
|
|
416
428
|
export function pickVersion(packument, range) {
|
|
417
429
|
// dist-tag fast path: `latest`, `next`, ...
|
|
418
|
-
if (packument[
|
|
419
|
-
return packument[
|
|
430
|
+
if (packument['dist-tags'][range])
|
|
431
|
+
return packument['dist-tags'][range];
|
|
420
432
|
// Validate range early so a typo fails loudly.
|
|
421
433
|
let parsedRange;
|
|
422
434
|
try {
|
|
@@ -440,8 +452,7 @@ async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
|
|
|
440
452
|
// Sort by install-path depth ascending so parents extract before
|
|
441
453
|
// children. Extracting a parent on top of an existing child would
|
|
442
454
|
// wipe out the child.
|
|
443
|
-
const queue = [...nodes].sort((a, b) => depth(a.installPath) - depth(b.installPath) ||
|
|
444
|
-
(a.installPath < b.installPath ? -1 : 1));
|
|
455
|
+
const queue = [...nodes].sort((a, b) => depth(a.installPath) - depth(b.installPath) || (a.installPath < b.installPath ? -1 : 1));
|
|
445
456
|
const workers = [];
|
|
446
457
|
const concurrency = Math.max(1, Math.min(DEFAULT_CONCURRENCY, queue.length));
|
|
447
458
|
// Parents (depth 1) are extracted serially first to avoid concurrent
|
|
@@ -475,12 +486,12 @@ async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
|
|
|
475
486
|
}
|
|
476
487
|
async function extractOne(node, prefix, npmrc, log) {
|
|
477
488
|
const dest = path.join(prefix, node.installPath);
|
|
478
|
-
log(
|
|
489
|
+
log('fetch: %s@%s ← %s (→ %s)', node.name, node.version, node.tarballUrl, node.installPath);
|
|
479
490
|
const bytes = await fetchTarball(node.tarballUrl, {
|
|
480
491
|
npmrc,
|
|
481
492
|
integrity: node.integrity,
|
|
482
493
|
onRetry: ({ attempt, error, delayMs }) => {
|
|
483
|
-
log(
|
|
494
|
+
log('tarball %s@%s: retry %d after %dms (%s)', node.name, node.version, attempt, delayMs, errMsg(error));
|
|
484
495
|
},
|
|
485
496
|
});
|
|
486
497
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
@@ -490,7 +501,7 @@ async function extractOne(node, prefix, npmrc, log) {
|
|
|
490
501
|
function depth(installPath) {
|
|
491
502
|
// Count `node_modules/` segments to know nesting depth.
|
|
492
503
|
// `node_modules/foo` = 1, `node_modules/foo/node_modules/bar` = 2, etc.
|
|
493
|
-
return installPath.split(
|
|
504
|
+
return installPath.split('/node_modules/').length;
|
|
494
505
|
}
|
|
495
506
|
async function linkBins(nodes, prefix, log) {
|
|
496
507
|
// Only root-level packages publish bins into the top-level
|
|
@@ -498,7 +509,7 @@ async function linkBins(nodes, prefix, log) {
|
|
|
498
509
|
// direct dependents through the nested .bin (npm matches this) — we
|
|
499
510
|
// omit nested-bin linking for now since no consumer of the install
|
|
500
511
|
// backend depends on it (gjsify's own use cases all hit root bins).
|
|
501
|
-
const binDir = path.join(prefix,
|
|
512
|
+
const binDir = path.join(prefix, 'node_modules', '.bin');
|
|
502
513
|
let created = 0;
|
|
503
514
|
for (const node of nodes) {
|
|
504
515
|
if (!node.bin)
|
|
@@ -534,15 +545,13 @@ async function linkBins(nodes, prefix, log) {
|
|
|
534
545
|
}
|
|
535
546
|
}
|
|
536
547
|
if (created > 0)
|
|
537
|
-
log(
|
|
548
|
+
log('bin: linked %d entry(ies) under .bin/', created);
|
|
538
549
|
}
|
|
539
550
|
function normalizeBin(pkgName, bin) {
|
|
540
551
|
const out = new Map();
|
|
541
|
-
if (typeof bin ===
|
|
552
|
+
if (typeof bin === 'string') {
|
|
542
553
|
// String form is shorthand for `{ <last-segment-of-pkgName>: <bin> }`.
|
|
543
|
-
const baseName = pkgName.startsWith(
|
|
544
|
-
? pkgName.slice(pkgName.indexOf("/") + 1)
|
|
545
|
-
: pkgName;
|
|
554
|
+
const baseName = pkgName.startsWith('@') ? pkgName.slice(pkgName.indexOf('/') + 1) : pkgName;
|
|
546
555
|
out.set(baseName, bin);
|
|
547
556
|
return out;
|
|
548
557
|
}
|
|
@@ -563,11 +572,11 @@ async function loadNpmrc(opts) {
|
|
|
563
572
|
// workspace-root one too; the gjsify project-local case is what users
|
|
564
573
|
// hit most often (mock-registry tests, scoped-registry overrides), so
|
|
565
574
|
// we cover that explicitly.
|
|
566
|
-
for (const candidate of [path.join(home,
|
|
575
|
+
for (const candidate of [path.join(home, '.npmrc'), path.join(opts.prefix, '.npmrc')]) {
|
|
567
576
|
if (!fs.existsSync(candidate))
|
|
568
577
|
continue;
|
|
569
578
|
try {
|
|
570
|
-
const projectParsed = parseNpmrc(fs.readFileSync(candidate,
|
|
579
|
+
const projectParsed = parseNpmrc(fs.readFileSync(candidate, 'utf-8'));
|
|
571
580
|
parsed = { ...parsed, ...projectParsed, scopes: { ...parsed.scopes, ...projectParsed.scopes } };
|
|
572
581
|
}
|
|
573
582
|
catch (e) {
|
|
@@ -27,6 +27,19 @@ export interface InstallOptions {
|
|
|
27
27
|
* forcing `typescript@~5.9` across every `typescript@*` devDep.
|
|
28
28
|
*/
|
|
29
29
|
overrides?: Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Native backend only: skip transitive dependency resolution and only
|
|
32
|
+
* install the top-level requested packages. Use this when the packages
|
|
33
|
+
* are self-contained bundles whose declared `dependencies` are either
|
|
34
|
+
* bundled into the artifact (e.g. `@gjsify/cli`'s GJS bundle) or
|
|
35
|
+
* workspace-only packages not published to npm separately. Setting this
|
|
36
|
+
* avoids spurious packument fetches for workspace-internal packages.
|
|
37
|
+
*
|
|
38
|
+
* Has no effect when `frozen: true` (the lockfile already contains the
|
|
39
|
+
* full resolved tree and is used verbatim) or when `GJSIFY_INSTALL_BACKEND=npm`
|
|
40
|
+
* (npm does its own resolution and does not consult this flag).
|
|
41
|
+
*/
|
|
42
|
+
skipDeps?: boolean;
|
|
30
43
|
}
|
|
31
44
|
export interface InstallResult {
|
|
32
45
|
/** Top-level packages that were requested, with the version each
|
|
@@ -40,7 +40,8 @@ async function installViaNpm({ prefix, specs, verbose, registry }) {
|
|
|
40
40
|
'--no-package-lock',
|
|
41
41
|
'--no-audit',
|
|
42
42
|
'--no-fund',
|
|
43
|
-
'--prefix',
|
|
43
|
+
'--prefix',
|
|
44
|
+
prefix,
|
|
44
45
|
...(verbose ? ['--loglevel', 'verbose'] : ['--loglevel', 'warn']),
|
|
45
46
|
...specs,
|
|
46
47
|
];
|
|
@@ -161,9 +161,7 @@ function pickBinMap(pkgName, pkgJson) {
|
|
|
161
161
|
function normalizeBin(pkgName, bin) {
|
|
162
162
|
const out = new Map();
|
|
163
163
|
if (typeof bin === 'string') {
|
|
164
|
-
const baseName = pkgName.startsWith('@')
|
|
165
|
-
? pkgName.slice(pkgName.indexOf('/') + 1)
|
|
166
|
-
: pkgName;
|
|
164
|
+
const baseName = pkgName.startsWith('@') ? pkgName.slice(pkgName.indexOf('/') + 1) : pkgName;
|
|
167
165
|
out.set(baseName, bin);
|
|
168
166
|
return out;
|
|
169
167
|
}
|