@gjsify/cli 0.4.28 → 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 +33 -33
- 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.js +17 -18
- package/lib/commands/run.js +2 -6
- package/lib/commands/self-update.js +1 -3
- 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 +3 -1
- 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 +65 -58
- 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(
|
|
58
|
+
log('install: resolving %d top-level spec(s) → %s', opts.specs.length, opts.prefix);
|
|
59
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
|
/**
|
|
@@ -128,7 +128,7 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
|
|
|
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, skipDeps) {
|
|
|
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,13 +200,23 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
|
|
|
200
200
|
if (installPath === `node_modules/${edge.name}`) {
|
|
201
201
|
root.set(edge.name, node);
|
|
202
202
|
}
|
|
203
|
-
log(
|
|
203
|
+
log('resolve: %s@%s ← %s (at %s)', edge.name, version, edge.range, installPath);
|
|
204
204
|
if (!skipDeps) {
|
|
205
205
|
for (const [depName, depRange] of Object.entries(node.dependencies)) {
|
|
206
|
-
queue.push({
|
|
206
|
+
queue.push({
|
|
207
|
+
from: installPath,
|
|
208
|
+
name: depName,
|
|
209
|
+
range: applyOverride(depName, depRange),
|
|
210
|
+
required: true,
|
|
211
|
+
});
|
|
207
212
|
}
|
|
208
213
|
for (const [depName, depRange] of Object.entries(node.optionalDependencies)) {
|
|
209
|
-
queue.push({
|
|
214
|
+
queue.push({
|
|
215
|
+
from: installPath,
|
|
216
|
+
name: depName,
|
|
217
|
+
range: applyOverride(depName, depRange),
|
|
218
|
+
required: false,
|
|
219
|
+
});
|
|
210
220
|
}
|
|
211
221
|
}
|
|
212
222
|
}
|
|
@@ -214,7 +224,7 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
|
|
|
214
224
|
// Optional deps that fail to resolve are skipped — yarn/npm
|
|
215
225
|
// behavior. Required deps re-throw.
|
|
216
226
|
if (!edge.required) {
|
|
217
|
-
log(
|
|
227
|
+
log('resolve: optional dep %s@%s skipped (%s)', edge.name, edge.range, e.message);
|
|
218
228
|
continue;
|
|
219
229
|
}
|
|
220
230
|
throw e;
|
|
@@ -245,12 +255,12 @@ function findVisible(requesterPath, name, byPath) {
|
|
|
245
255
|
// eslint-disable-next-line no-constant-condition
|
|
246
256
|
while (true) {
|
|
247
257
|
// Find the deepest `/node_modules/<pkg>` in `p`, strip it.
|
|
248
|
-
const idx = p.lastIndexOf(
|
|
258
|
+
const idx = p.lastIndexOf('/node_modules/');
|
|
249
259
|
if (idx < 0)
|
|
250
260
|
break;
|
|
251
261
|
p = p.slice(0, idx);
|
|
252
262
|
candidates.push(`${p}/node_modules/${name}`);
|
|
253
|
-
if (p ===
|
|
263
|
+
if (p === '')
|
|
254
264
|
break;
|
|
255
265
|
}
|
|
256
266
|
}
|
|
@@ -301,10 +311,10 @@ function readLockfile(lockfilePath) {
|
|
|
301
311
|
if (!fs.existsSync(lockfilePath))
|
|
302
312
|
return null;
|
|
303
313
|
try {
|
|
304
|
-
const parsed = JSON.parse(fs.readFileSync(lockfilePath,
|
|
314
|
+
const parsed = JSON.parse(fs.readFileSync(lockfilePath, 'utf-8'));
|
|
305
315
|
if (parsed.lockfileVersion !== LOCKFILE_VERSION)
|
|
306
316
|
return null;
|
|
307
|
-
if (!parsed.packages || typeof parsed.packages !==
|
|
317
|
+
if (!parsed.packages || typeof parsed.packages !== 'object')
|
|
308
318
|
return null;
|
|
309
319
|
return parsed;
|
|
310
320
|
}
|
|
@@ -330,7 +340,7 @@ function writeLockfile(lockfilePath, specs, nodes) {
|
|
|
330
340
|
requested: [...specs],
|
|
331
341
|
packages,
|
|
332
342
|
};
|
|
333
|
-
fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) +
|
|
343
|
+
fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) + '\n');
|
|
334
344
|
}
|
|
335
345
|
function lockfileToNodes(lockfile) {
|
|
336
346
|
return Object.entries(lockfile.packages).map(([installPath, entry]) => ({
|
|
@@ -349,8 +359,8 @@ function lockfileToNodes(lockfile) {
|
|
|
349
359
|
function nameFromInstallPath(installPath) {
|
|
350
360
|
// Last `node_modules/` boundary, then the rest is the package name
|
|
351
361
|
// (single segment unscoped, or `@scope/pkg` scoped).
|
|
352
|
-
const idx = installPath.lastIndexOf(
|
|
353
|
-
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);
|
|
354
364
|
return after;
|
|
355
365
|
}
|
|
356
366
|
function lockfileMatchesRequest(lockfile, specs) {
|
|
@@ -381,10 +391,10 @@ function describeLockfileDrift(lockfile, specs) {
|
|
|
381
391
|
return null;
|
|
382
392
|
const lines = [];
|
|
383
393
|
if (added.length > 0)
|
|
384
|
-
lines.push(` + ${added.sort().join(
|
|
394
|
+
lines.push(` + ${added.sort().join('\n + ')}`);
|
|
385
395
|
if (removed.length > 0)
|
|
386
|
-
lines.push(` - ${removed.sort().join(
|
|
387
|
-
return lines.join(
|
|
396
|
+
lines.push(` - ${removed.sort().join('\n - ')}`);
|
|
397
|
+
return lines.join('\n');
|
|
388
398
|
}
|
|
389
399
|
// Exported for unit-testing — keep the function name + signature
|
|
390
400
|
// stable, the install-backend itself still calls it via the local
|
|
@@ -400,25 +410,25 @@ export function parseSpec(raw) {
|
|
|
400
410
|
// shipped only prereleases (4.0.0-rc.17 is the `latest` tag, no
|
|
401
411
|
// stable 4.x yet) and `*` was selecting the abandoned 3.3.0
|
|
402
412
|
// instead.
|
|
403
|
-
if (raw.startsWith(
|
|
404
|
-
const slash = raw.indexOf(
|
|
413
|
+
if (raw.startsWith('@')) {
|
|
414
|
+
const slash = raw.indexOf('/');
|
|
405
415
|
if (slash < 0)
|
|
406
416
|
throw new Error(`Invalid spec (scoped name without slash): ${raw}`);
|
|
407
|
-
const at = raw.indexOf(
|
|
417
|
+
const at = raw.indexOf('@', slash);
|
|
408
418
|
if (at < 0)
|
|
409
|
-
return { name: raw, range:
|
|
410
|
-
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' };
|
|
411
421
|
}
|
|
412
|
-
const at = raw.indexOf(
|
|
422
|
+
const at = raw.indexOf('@');
|
|
413
423
|
if (at < 0)
|
|
414
|
-
return { name: raw, range:
|
|
415
|
-
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' };
|
|
416
426
|
}
|
|
417
427
|
// Exported for unit-testing. Internal API.
|
|
418
428
|
export function pickVersion(packument, range) {
|
|
419
429
|
// dist-tag fast path: `latest`, `next`, ...
|
|
420
|
-
if (packument[
|
|
421
|
-
return packument[
|
|
430
|
+
if (packument['dist-tags'][range])
|
|
431
|
+
return packument['dist-tags'][range];
|
|
422
432
|
// Validate range early so a typo fails loudly.
|
|
423
433
|
let parsedRange;
|
|
424
434
|
try {
|
|
@@ -442,8 +452,7 @@ async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
|
|
|
442
452
|
// Sort by install-path depth ascending so parents extract before
|
|
443
453
|
// children. Extracting a parent on top of an existing child would
|
|
444
454
|
// wipe out the child.
|
|
445
|
-
const queue = [...nodes].sort((a, b) => depth(a.installPath) - depth(b.installPath) ||
|
|
446
|
-
(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));
|
|
447
456
|
const workers = [];
|
|
448
457
|
const concurrency = Math.max(1, Math.min(DEFAULT_CONCURRENCY, queue.length));
|
|
449
458
|
// Parents (depth 1) are extracted serially first to avoid concurrent
|
|
@@ -477,12 +486,12 @@ async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
|
|
|
477
486
|
}
|
|
478
487
|
async function extractOne(node, prefix, npmrc, log) {
|
|
479
488
|
const dest = path.join(prefix, node.installPath);
|
|
480
|
-
log(
|
|
489
|
+
log('fetch: %s@%s ← %s (→ %s)', node.name, node.version, node.tarballUrl, node.installPath);
|
|
481
490
|
const bytes = await fetchTarball(node.tarballUrl, {
|
|
482
491
|
npmrc,
|
|
483
492
|
integrity: node.integrity,
|
|
484
493
|
onRetry: ({ attempt, error, delayMs }) => {
|
|
485
|
-
log(
|
|
494
|
+
log('tarball %s@%s: retry %d after %dms (%s)', node.name, node.version, attempt, delayMs, errMsg(error));
|
|
486
495
|
},
|
|
487
496
|
});
|
|
488
497
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
@@ -492,7 +501,7 @@ async function extractOne(node, prefix, npmrc, log) {
|
|
|
492
501
|
function depth(installPath) {
|
|
493
502
|
// Count `node_modules/` segments to know nesting depth.
|
|
494
503
|
// `node_modules/foo` = 1, `node_modules/foo/node_modules/bar` = 2, etc.
|
|
495
|
-
return installPath.split(
|
|
504
|
+
return installPath.split('/node_modules/').length;
|
|
496
505
|
}
|
|
497
506
|
async function linkBins(nodes, prefix, log) {
|
|
498
507
|
// Only root-level packages publish bins into the top-level
|
|
@@ -500,7 +509,7 @@ async function linkBins(nodes, prefix, log) {
|
|
|
500
509
|
// direct dependents through the nested .bin (npm matches this) — we
|
|
501
510
|
// omit nested-bin linking for now since no consumer of the install
|
|
502
511
|
// backend depends on it (gjsify's own use cases all hit root bins).
|
|
503
|
-
const binDir = path.join(prefix,
|
|
512
|
+
const binDir = path.join(prefix, 'node_modules', '.bin');
|
|
504
513
|
let created = 0;
|
|
505
514
|
for (const node of nodes) {
|
|
506
515
|
if (!node.bin)
|
|
@@ -536,15 +545,13 @@ async function linkBins(nodes, prefix, log) {
|
|
|
536
545
|
}
|
|
537
546
|
}
|
|
538
547
|
if (created > 0)
|
|
539
|
-
log(
|
|
548
|
+
log('bin: linked %d entry(ies) under .bin/', created);
|
|
540
549
|
}
|
|
541
550
|
function normalizeBin(pkgName, bin) {
|
|
542
551
|
const out = new Map();
|
|
543
|
-
if (typeof bin ===
|
|
552
|
+
if (typeof bin === 'string') {
|
|
544
553
|
// String form is shorthand for `{ <last-segment-of-pkgName>: <bin> }`.
|
|
545
|
-
const baseName = pkgName.startsWith(
|
|
546
|
-
? pkgName.slice(pkgName.indexOf("/") + 1)
|
|
547
|
-
: pkgName;
|
|
554
|
+
const baseName = pkgName.startsWith('@') ? pkgName.slice(pkgName.indexOf('/') + 1) : pkgName;
|
|
548
555
|
out.set(baseName, bin);
|
|
549
556
|
return out;
|
|
550
557
|
}
|
|
@@ -565,11 +572,11 @@ async function loadNpmrc(opts) {
|
|
|
565
572
|
// workspace-root one too; the gjsify project-local case is what users
|
|
566
573
|
// hit most often (mock-registry tests, scoped-registry overrides), so
|
|
567
574
|
// we cover that explicitly.
|
|
568
|
-
for (const candidate of [path.join(home,
|
|
575
|
+
for (const candidate of [path.join(home, '.npmrc'), path.join(opts.prefix, '.npmrc')]) {
|
|
569
576
|
if (!fs.existsSync(candidate))
|
|
570
577
|
continue;
|
|
571
578
|
try {
|
|
572
|
-
const projectParsed = parseNpmrc(fs.readFileSync(candidate,
|
|
579
|
+
const projectParsed = parseNpmrc(fs.readFileSync(candidate, 'utf-8'));
|
|
573
580
|
parsed = { ...parsed, ...projectParsed, scopes: { ...parsed.scopes, ...projectParsed.scopes } };
|
|
574
581
|
}
|
|
575
582
|
catch (e) {
|
|
@@ -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
|
}
|