@gjsify/cli 0.4.4 → 0.4.9
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 +14 -11
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/install.js +53 -0
- package/lib/commands/pack.d.ts +40 -0
- package/lib/commands/pack.js +335 -0
- package/lib/commands/publish.d.ts +12 -0
- package/lib/commands/publish.js +319 -0
- package/lib/index.js +3 -1
- package/lib/utils/install-backend-native.js +16 -5
- package/lib/utils/install-backend.d.ts +12 -0
- package/package.json +15 -15
package/lib/commands/index.d.ts
CHANGED
package/lib/commands/index.js
CHANGED
package/lib/commands/install.js
CHANGED
|
@@ -248,6 +248,13 @@ async function workspaceInstall(cwd, args) {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
console.log(`gjsify install: ${workspaces.length} workspace(s), ${externalSpecs.size} external dep spec(s), ${symlinks.length} workspace symlink(s)`);
|
|
251
|
+
// Read top-level package.json's `overrides` (npm-native) or `resolutions`
|
|
252
|
+
// (yarn-native, kept as the existing field name in pre-Phase-D.8 repos).
|
|
253
|
+
// Both are flattened to a name → version map and passed to the install
|
|
254
|
+
// backend. Pattern keys like `typescript@*` are normalised to bare names —
|
|
255
|
+
// we don't yet support per-parent scoping (npm's nested overrides shape).
|
|
256
|
+
const rootManifest = workspaces.find((w) => w.location === cwd)?.manifest;
|
|
257
|
+
const overrides = extractOverrides(rootManifest);
|
|
251
258
|
if (externalSpecs.size > 0) {
|
|
252
259
|
await installPackages({
|
|
253
260
|
prefix: cwd,
|
|
@@ -255,6 +262,7 @@ async function workspaceInstall(cwd, args) {
|
|
|
255
262
|
verbose: args.verbose,
|
|
256
263
|
lockfile: !args.immutable,
|
|
257
264
|
frozen: args.immutable,
|
|
265
|
+
overrides,
|
|
258
266
|
});
|
|
259
267
|
}
|
|
260
268
|
else if (args.verbose) {
|
|
@@ -386,6 +394,51 @@ async function workspaceInstall(cwd, args) {
|
|
|
386
394
|
* different cwds that consumers (`yarn run`, `npm run`, direct PATH
|
|
387
395
|
* invocation) call us from.
|
|
388
396
|
*/
|
|
397
|
+
/**
|
|
398
|
+
* Flatten npm `overrides` or yarn `resolutions` into a bare name → range map.
|
|
399
|
+
*
|
|
400
|
+
* Supports two input shapes:
|
|
401
|
+
*
|
|
402
|
+
* "overrides": { "typescript": "~5.9.2" } (npm)
|
|
403
|
+
* "resolutions": { "typescript@*": "~5.9.2" } (yarn pattern)
|
|
404
|
+
*
|
|
405
|
+
* Pattern keys with a version glob (`name@*`, `name@^x`) are normalised to the
|
|
406
|
+
* bare name — gjsify's resolver doesn't yet support per-incoming-range
|
|
407
|
+
* scoping. Object-valued nested overrides (npm's per-parent shape, e.g.
|
|
408
|
+
* `"foo": { ".": "1.0", "bar": "2.0" }`) are intentionally ignored; they would
|
|
409
|
+
* silently misbehave without per-parent support, so we surface a warning
|
|
410
|
+
* instead of half-applying them.
|
|
411
|
+
*
|
|
412
|
+
* Keys beginning with `_` are skipped (convention for documentation entries
|
|
413
|
+
* like `"_comment_typescript"` used in the wild).
|
|
414
|
+
*/
|
|
415
|
+
function extractOverrides(rootManifest) {
|
|
416
|
+
if (!rootManifest)
|
|
417
|
+
return undefined;
|
|
418
|
+
const out = {};
|
|
419
|
+
const merge = (source, fieldName) => {
|
|
420
|
+
if (!source)
|
|
421
|
+
return;
|
|
422
|
+
for (const [key, value] of Object.entries(source)) {
|
|
423
|
+
if (key.startsWith('_'))
|
|
424
|
+
continue;
|
|
425
|
+
if (typeof value !== 'string') {
|
|
426
|
+
console.warn(`gjsify install: ${fieldName}["${key}"] is not a string — nested override shape isn't supported yet, skipping`);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
// Normalise pattern keys (`name@*`, `name@^range`) → bare name.
|
|
430
|
+
// For scoped packages preserve the leading `@`.
|
|
431
|
+
let name = key;
|
|
432
|
+
const atIdx = key.startsWith('@') ? key.indexOf('@', 1) : key.indexOf('@');
|
|
433
|
+
if (atIdx > 0)
|
|
434
|
+
name = key.slice(0, atIdx);
|
|
435
|
+
out[name] = value;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
merge(rootManifest.overrides, 'overrides');
|
|
439
|
+
merge(rootManifest.resolutions, 'resolutions');
|
|
440
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
441
|
+
}
|
|
389
442
|
function buildBinShim(wsLocation, nodeTarget, gjsTarget) {
|
|
390
443
|
const nodeAbs = nodeTarget ? join(wsLocation, nodeTarget) : null;
|
|
391
444
|
const gjsAbs = gjsTarget ? join(wsLocation, gjsTarget) : null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface PackOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
'pack-destination'?: string;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
'dry-run'?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface PackResult {
|
|
9
|
+
filename: string;
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
size: number;
|
|
13
|
+
unpackedSize: number;
|
|
14
|
+
shasum: string;
|
|
15
|
+
integrity: string;
|
|
16
|
+
entryCount: number;
|
|
17
|
+
files: {
|
|
18
|
+
path: string;
|
|
19
|
+
size: number;
|
|
20
|
+
mode: number;
|
|
21
|
+
}[];
|
|
22
|
+
/** Absolute path of the written .tgz, or null on --dry-run. */
|
|
23
|
+
absolutePath: string | null;
|
|
24
|
+
}
|
|
25
|
+
export declare const packCommand: Command<any, PackOptions>;
|
|
26
|
+
export interface PackWorkspaceOptions {
|
|
27
|
+
/** Directory to write the .tgz into. Defaults to the workspace itself. */
|
|
28
|
+
destination?: string;
|
|
29
|
+
/** Skip writing the .tgz; metadata is still computed (for npm-compat callers). */
|
|
30
|
+
dryRun?: boolean;
|
|
31
|
+
/** Skip the workspace:^ rewrite step (rare — useful for testing the raw layout). */
|
|
32
|
+
skipWorkspaceRewrite?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Programmatic equivalent of the `pack` command — used by `gjsify publish`
|
|
36
|
+
* to avoid spawning a subprocess. Caller is responsible for resolving
|
|
37
|
+
* `wsDir` to an absolute path.
|
|
38
|
+
*/
|
|
39
|
+
export declare function packWorkspace(wsDir: string, opts?: PackWorkspaceOptions): Promise<PackResult>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// `gjsify pack [path] [--pack-destination <dir>] [--json]` — npm-compatible
|
|
2
|
+
// tarball creation for the workspace at `path` (default: cwd).
|
|
3
|
+
//
|
|
4
|
+
// Output shape matches `npm pack --json`:
|
|
5
|
+
// [
|
|
6
|
+
// {
|
|
7
|
+
// "filename": "scope-name-1.2.3.tgz",
|
|
8
|
+
// "name": "@scope/name",
|
|
9
|
+
// "version": "1.2.3",
|
|
10
|
+
// "size": <unpacked bytes>,
|
|
11
|
+
// "unpackedSize": <unpacked bytes>,
|
|
12
|
+
// "shasum": "<sha1 hex>",
|
|
13
|
+
// "integrity": "sha512-<base64>",
|
|
14
|
+
// "files": [ { path, size, mode }, ... ],
|
|
15
|
+
// "entryCount": <int>
|
|
16
|
+
// }
|
|
17
|
+
// ]
|
|
18
|
+
//
|
|
19
|
+
// File selection mirrors npm pack: explicit `files` field if present, else
|
|
20
|
+
// the default allowlist (README/LICENSE/package.json + main entry + bin). The
|
|
21
|
+
// implementation here is conservative — when no `files` field is set we walk
|
|
22
|
+
// the workspace recursively and apply the implicit-exclusion set
|
|
23
|
+
// (.git, node_modules, …) plus .npmignore / .gitignore. workspace:^ deps in
|
|
24
|
+
// package.json are rewritten to resolved npm version ranges based on the
|
|
25
|
+
// sibling workspaces' own versions, so the published tarball is consumable.
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { join, resolve } from 'node:path';
|
|
29
|
+
import { createTarball, gzip } from '@gjsify/tar';
|
|
30
|
+
import { discoverWorkspaces } from '@gjsify/workspace';
|
|
31
|
+
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
32
|
+
export const packCommand = {
|
|
33
|
+
command: 'pack [path]',
|
|
34
|
+
description: 'Produce an npm-compatible .tgz tarball for the workspace at <path> (default: cwd). Rewrites workspace:^/~/* deps to resolved versions.',
|
|
35
|
+
builder: (yargs) => yargs
|
|
36
|
+
.positional('path', {
|
|
37
|
+
description: 'Workspace path (default: cwd).',
|
|
38
|
+
type: 'string',
|
|
39
|
+
})
|
|
40
|
+
.option('pack-destination', {
|
|
41
|
+
description: 'Directory to write the tarball into. Default: workspace cwd.',
|
|
42
|
+
type: 'string',
|
|
43
|
+
})
|
|
44
|
+
.option('json', {
|
|
45
|
+
description: 'Emit pack metadata as JSON on stdout (mirrors `npm pack --json`).',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: false,
|
|
48
|
+
})
|
|
49
|
+
.option('dry-run', {
|
|
50
|
+
description: 'Compute everything but do not write the .tgz.',
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
default: false,
|
|
53
|
+
}),
|
|
54
|
+
handler: async (args) => {
|
|
55
|
+
const wsDir = resolve(args.path ?? process.cwd());
|
|
56
|
+
const result = await packWorkspace(wsDir, {
|
|
57
|
+
destination: args['pack-destination'],
|
|
58
|
+
dryRun: args['dry-run'] === true,
|
|
59
|
+
});
|
|
60
|
+
if (args.json) {
|
|
61
|
+
process.stdout.write(`${JSON.stringify([result], null, 2)}\n`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
process.stdout.write(`${result.filename}\n`);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Programmatic equivalent of the `pack` command — used by `gjsify publish`
|
|
70
|
+
* to avoid spawning a subprocess. Caller is responsible for resolving
|
|
71
|
+
* `wsDir` to an absolute path.
|
|
72
|
+
*/
|
|
73
|
+
export async function packWorkspace(wsDir, opts = {}) {
|
|
74
|
+
const pkgPath = join(wsDir, 'package.json');
|
|
75
|
+
if (!existsSync(pkgPath)) {
|
|
76
|
+
throw new Error(`gjsify pack: no package.json at ${wsDir}`);
|
|
77
|
+
}
|
|
78
|
+
const originalSource = readFileSync(pkgPath, 'utf-8');
|
|
79
|
+
const pkg = JSON.parse(originalSource);
|
|
80
|
+
const name = typeof pkg.name === 'string' ? pkg.name : '';
|
|
81
|
+
const version = typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
|
82
|
+
if (!name) {
|
|
83
|
+
throw new Error(`gjsify pack: package.json at ${wsDir} has no "name"`);
|
|
84
|
+
}
|
|
85
|
+
// Rewrite workspace:^/~/* deps to resolved npm version ranges, mirroring
|
|
86
|
+
// yarn's auto-rewrite at publish time. Done in-memory only — the source
|
|
87
|
+
// package.json on disk is never mutated by `gjsify pack`.
|
|
88
|
+
const rewrittenPkg = opts.skipWorkspaceRewrite
|
|
89
|
+
? pkg
|
|
90
|
+
: rewriteWorkspaceDeps(pkg, wsDir);
|
|
91
|
+
const rewrittenSource = JSON.stringify(rewrittenPkg, null, indentOf(originalSource)) + '\n';
|
|
92
|
+
// Collect files according to the package.json `files` field (or npm's
|
|
93
|
+
// default set). The package.json itself is always included with the
|
|
94
|
+
// rewritten contents.
|
|
95
|
+
const filesToPack = collectFiles(wsDir, pkg);
|
|
96
|
+
const entries = [{ name: 'package/', directory: true, mode: 0o755 }];
|
|
97
|
+
const fileMetas = [];
|
|
98
|
+
let unpackedSize = 0;
|
|
99
|
+
for (const rel of filesToPack) {
|
|
100
|
+
let body;
|
|
101
|
+
if (rel === 'package.json') {
|
|
102
|
+
body = new TextEncoder().encode(rewrittenSource);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
body = new Uint8Array(readFileSync(join(wsDir, rel)));
|
|
106
|
+
}
|
|
107
|
+
const st = statSync(join(wsDir, rel));
|
|
108
|
+
const mode = st.mode & 0o777;
|
|
109
|
+
entries.push({ name: `package/${rel}`, body, mode, mtime: 0 });
|
|
110
|
+
fileMetas.push({ path: rel, size: body.byteLength, mode });
|
|
111
|
+
unpackedSize += body.byteLength;
|
|
112
|
+
}
|
|
113
|
+
const tarBytes = createTarball(entries);
|
|
114
|
+
const gzipBytes = await gzip(tarBytes);
|
|
115
|
+
// npm filename: scope replaced with leading dash. "@gjsify/foo" → "gjsify-foo".
|
|
116
|
+
const filenameBase = name.startsWith('@')
|
|
117
|
+
? name.slice(1).replace('/', '-')
|
|
118
|
+
: name;
|
|
119
|
+
const filename = `${filenameBase}-${version}.tgz`;
|
|
120
|
+
const sha1 = createHash('sha1').update(gzipBytes).digest('hex');
|
|
121
|
+
const sha512 = createHash('sha512').update(gzipBytes).digest('base64');
|
|
122
|
+
const integrity = `sha512-${sha512}`;
|
|
123
|
+
const destDir = opts.destination ? resolve(opts.destination) : wsDir;
|
|
124
|
+
const tarPath = join(destDir, filename);
|
|
125
|
+
if (!opts.dryRun) {
|
|
126
|
+
mkdirSync(destDir, { recursive: true });
|
|
127
|
+
writeFileSync(tarPath, gzipBytes);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
filename,
|
|
131
|
+
name,
|
|
132
|
+
version,
|
|
133
|
+
size: gzipBytes.byteLength,
|
|
134
|
+
unpackedSize,
|
|
135
|
+
shasum: sha1,
|
|
136
|
+
integrity,
|
|
137
|
+
entryCount: fileMetas.length,
|
|
138
|
+
files: fileMetas,
|
|
139
|
+
absolutePath: opts.dryRun ? null : tarPath,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Walk the workspace and return the list of files to include in the tarball,
|
|
144
|
+
* relative to `wsDir`. Mirrors npm pack's selection rules:
|
|
145
|
+
*
|
|
146
|
+
* - If pkg.files exists: use it as an allowlist (with .npmignore as a
|
|
147
|
+
* blacklist within those globs)
|
|
148
|
+
* - Otherwise: walk everything, apply .npmignore + .gitignore, drop the
|
|
149
|
+
* implicit-exclusion set (node_modules, .git, …)
|
|
150
|
+
*
|
|
151
|
+
* package.json, README*, LICENSE*, NOTICE* and the `bin`/`main` files are
|
|
152
|
+
* always force-included regardless of the rules above.
|
|
153
|
+
*/
|
|
154
|
+
function collectFiles(wsDir, pkg) {
|
|
155
|
+
const always = forceIncluded(pkg);
|
|
156
|
+
const filesField = Array.isArray(pkg.files) ? pkg.files.filter((f) => typeof f === 'string') : null;
|
|
157
|
+
let candidates;
|
|
158
|
+
if (filesField) {
|
|
159
|
+
candidates = expandFilesPatterns(wsDir, filesField);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
candidates = walkAll(wsDir);
|
|
163
|
+
}
|
|
164
|
+
const ignore = loadIgnore(wsDir);
|
|
165
|
+
const out = new Set();
|
|
166
|
+
for (const f of candidates) {
|
|
167
|
+
if (!ignore(f))
|
|
168
|
+
out.add(f);
|
|
169
|
+
}
|
|
170
|
+
for (const f of always) {
|
|
171
|
+
if (existsSync(join(wsDir, f)))
|
|
172
|
+
out.add(f);
|
|
173
|
+
}
|
|
174
|
+
return [...out].sort();
|
|
175
|
+
}
|
|
176
|
+
const ALWAYS_INCLUDED_BASENAMES = new Set(['package.json', 'README', 'README.md', 'LICENSE', 'LICENSE.md', 'NOTICE', 'NOTICE.md']);
|
|
177
|
+
const NEVER_INCLUDED_BASENAMES = new Set([
|
|
178
|
+
'.git', '.svn', '.hg', '.gitignore', '.gitattributes', '.npmrc',
|
|
179
|
+
'CVS', '.DS_Store', 'node_modules', '.npmignore', 'package-lock.json',
|
|
180
|
+
'gjsify-lock.json', 'yarn.lock', 'yarn-error.log', '.yarn',
|
|
181
|
+
'.pnp.cjs', '.pnp.loader.mjs', 'tsconfig.tsbuildinfo',
|
|
182
|
+
]);
|
|
183
|
+
function forceIncluded(pkg) {
|
|
184
|
+
const out = new Set();
|
|
185
|
+
out.add('package.json');
|
|
186
|
+
for (const name of ['README', 'README.md', 'LICENSE', 'LICENSE.md', 'NOTICE', 'NOTICE.md']) {
|
|
187
|
+
out.add(name);
|
|
188
|
+
}
|
|
189
|
+
const main = typeof pkg.main === 'string' ? pkg.main : null;
|
|
190
|
+
if (main)
|
|
191
|
+
out.add(main.replace(/^\.\//, ''));
|
|
192
|
+
const bin = pkg.bin;
|
|
193
|
+
if (typeof bin === 'string') {
|
|
194
|
+
out.add(bin.replace(/^\.\//, ''));
|
|
195
|
+
}
|
|
196
|
+
else if (bin && typeof bin === 'object') {
|
|
197
|
+
for (const v of Object.values(bin)) {
|
|
198
|
+
if (typeof v === 'string')
|
|
199
|
+
out.add(v.replace(/^\.\//, ''));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [...out];
|
|
203
|
+
}
|
|
204
|
+
function walkAll(root, sub = '') {
|
|
205
|
+
const out = [];
|
|
206
|
+
const here = sub ? join(root, sub) : root;
|
|
207
|
+
let entries;
|
|
208
|
+
try {
|
|
209
|
+
entries = readdirSync(here, { withFileTypes: true });
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
if (NEVER_INCLUDED_BASENAMES.has(entry.name))
|
|
216
|
+
continue;
|
|
217
|
+
if (entry.name.startsWith('.tsbuildinfo'))
|
|
218
|
+
continue;
|
|
219
|
+
const rel = sub ? `${sub}/${entry.name}` : entry.name;
|
|
220
|
+
if (entry.isDirectory()) {
|
|
221
|
+
out.push(...walkAll(root, rel));
|
|
222
|
+
}
|
|
223
|
+
else if (entry.isFile()) {
|
|
224
|
+
out.push(rel);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function expandFilesPatterns(wsDir, patterns) {
|
|
230
|
+
const out = new Set();
|
|
231
|
+
for (const pattern of patterns) {
|
|
232
|
+
// Drop leading ./
|
|
233
|
+
const normalized = pattern.replace(/^\.\//, '').replace(/\/$/, '');
|
|
234
|
+
const full = join(wsDir, normalized);
|
|
235
|
+
if (!existsSync(full))
|
|
236
|
+
continue;
|
|
237
|
+
const st = statSync(full);
|
|
238
|
+
if (st.isDirectory()) {
|
|
239
|
+
for (const f of walkAll(wsDir, normalized))
|
|
240
|
+
out.add(f);
|
|
241
|
+
}
|
|
242
|
+
else if (st.isFile()) {
|
|
243
|
+
out.add(normalized);
|
|
244
|
+
}
|
|
245
|
+
// TODO: glob patterns (foo/*.js). Currently we treat the entry as a
|
|
246
|
+
// literal file or directory. Most monorepos use file/dir entries only
|
|
247
|
+
// (lib, dist, prebuilds) — globs are rare. Surface a warning if the
|
|
248
|
+
// pattern contains glob chars and didn't resolve.
|
|
249
|
+
if (!existsSync(full) && /[*?[]/.test(pattern)) {
|
|
250
|
+
console.warn(`gjsify pack: files entry "${pattern}" looks like a glob but glob expansion isn't implemented — pass literal files/dirs`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return [...out];
|
|
254
|
+
}
|
|
255
|
+
function loadIgnore(wsDir) {
|
|
256
|
+
// .npmignore takes precedence over .gitignore (npm semantics).
|
|
257
|
+
const npmIgnorePath = join(wsDir, '.npmignore');
|
|
258
|
+
const gitIgnorePath = join(wsDir, '.gitignore');
|
|
259
|
+
const patterns = [];
|
|
260
|
+
const sourcePath = existsSync(npmIgnorePath) ? npmIgnorePath : (existsSync(gitIgnorePath) ? gitIgnorePath : null);
|
|
261
|
+
if (sourcePath) {
|
|
262
|
+
const lines = readFileSync(sourcePath, 'utf-8').split('\n');
|
|
263
|
+
for (const raw of lines) {
|
|
264
|
+
const line = raw.trim();
|
|
265
|
+
if (!line || line.startsWith('#'))
|
|
266
|
+
continue;
|
|
267
|
+
// Simple translation: pattern → regex anchored to start, with `*` → `[^/]*`
|
|
268
|
+
// and `**` → `.*`. Negations (`!`) are skipped.
|
|
269
|
+
if (line.startsWith('!'))
|
|
270
|
+
continue;
|
|
271
|
+
patterns.push(globToRegex(line));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return (rel) => {
|
|
275
|
+
for (const p of patterns)
|
|
276
|
+
if (p.test(rel))
|
|
277
|
+
return true;
|
|
278
|
+
return false;
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function globToRegex(glob) {
|
|
282
|
+
// Strip leading / (anchors to root)
|
|
283
|
+
let pat = glob.replace(/^\//, '');
|
|
284
|
+
// Escape regex metachars except *,?,/
|
|
285
|
+
pat = pat.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
286
|
+
// ** → .* * → [^/]* ? → [^/]
|
|
287
|
+
pat = pat.replace(/\*\*/g, '__DOUBLESTAR__').replace(/\*/g, '[^/]*').replace(/__DOUBLESTAR__/g, '.*');
|
|
288
|
+
pat = pat.replace(/\?/g, '[^/]');
|
|
289
|
+
return new RegExp(`^${pat}($|/)`);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Walk pkg.dependencies / devDependencies / peerDependencies /
|
|
293
|
+
* optionalDependencies and replace `workspace:^` / `workspace:~` /
|
|
294
|
+
* `workspace:*` ranges with the resolved npm range based on each sibling
|
|
295
|
+
* workspace's version field.
|
|
296
|
+
*/
|
|
297
|
+
function rewriteWorkspaceDeps(pkg, wsDir) {
|
|
298
|
+
const root = findWorkspaceRoot(wsDir);
|
|
299
|
+
if (!root)
|
|
300
|
+
return pkg;
|
|
301
|
+
const siblings = new Map();
|
|
302
|
+
for (const ws of discoverWorkspaces(root)) {
|
|
303
|
+
if (ws.name && ws.version)
|
|
304
|
+
siblings.set(ws.name, ws.version);
|
|
305
|
+
}
|
|
306
|
+
const cloned = JSON.parse(JSON.stringify(pkg));
|
|
307
|
+
for (const block of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
308
|
+
const deps = cloned[block];
|
|
309
|
+
if (!deps)
|
|
310
|
+
continue;
|
|
311
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
312
|
+
if (typeof range !== 'string' || !range.startsWith('workspace:'))
|
|
313
|
+
continue;
|
|
314
|
+
const wsVer = siblings.get(name);
|
|
315
|
+
if (!wsVer) {
|
|
316
|
+
throw new Error(`gjsify pack: ${cloned.name} declares workspace:^ on ${name} but no sibling workspace with that name exists in the monorepo at ${root}`);
|
|
317
|
+
}
|
|
318
|
+
const operator = range.slice('workspace:'.length);
|
|
319
|
+
if (operator === '*' || operator === '') {
|
|
320
|
+
deps[name] = wsVer;
|
|
321
|
+
}
|
|
322
|
+
else if (operator === '^' || operator === '~') {
|
|
323
|
+
deps[name] = `${operator}${wsVer}`;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
deps[name] = operator;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return cloned;
|
|
331
|
+
}
|
|
332
|
+
function indentOf(source) {
|
|
333
|
+
const m = source.match(/\n([ \t]+)"/);
|
|
334
|
+
return m ? m[1] : ' ';
|
|
335
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface PublishOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
tag?: string;
|
|
5
|
+
access?: string;
|
|
6
|
+
'tolerate-republish'?: boolean;
|
|
7
|
+
provenance?: boolean;
|
|
8
|
+
'dry-run'?: boolean;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const publishCommand: Command<any, PublishOptions>;
|
|
12
|
+
export {};
|