@gjsify/cli 0.4.5 → 0.4.10
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 +134 -130
- package/lib/commands/generate-installer.d.ts +10 -0
- package/lib/commands/generate-installer.js +113 -0
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/index.js +4 -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/commands/self-update.d.ts +8 -0
- package/lib/commands/self-update.js +138 -0
- package/lib/index.js +5 -1
- package/lib/templates/install.mjs.tmpl +248 -0
- package/lib/utils/install-global.js +13 -1
- package/package.json +17 -17
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface GenerateInstallerOptions {
|
|
3
|
+
target?: string;
|
|
4
|
+
'bin-name'?: string;
|
|
5
|
+
'bootstrap-url'?: string;
|
|
6
|
+
output: string;
|
|
7
|
+
force: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const generateInstallerCommand: Command<any, GenerateInstallerOptions>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// `gjsify generate-installer [target]` — scaffold an `install.mjs` for any
|
|
2
|
+
// GJS-runnable npm package, modeled on gjsify's own installer.
|
|
3
|
+
//
|
|
4
|
+
// The generated `install.mjs` is a verbatim copy of gjsify's root `install.mjs`
|
|
5
|
+
// with three constants substituted:
|
|
6
|
+
//
|
|
7
|
+
// DEFAULT_TARGET → the consumer's npm package name
|
|
8
|
+
// DEFAULT_BIN_NAME → the consumer's bin name (key of `gjsify.bin` or `bin`)
|
|
9
|
+
// DEFAULT_BOOTSTRAP_URL → URL of a gjsify `cli.gjs.mjs` bootstrap bundle
|
|
10
|
+
//
|
|
11
|
+
// End-user workflow:
|
|
12
|
+
// cd my-gjs-app
|
|
13
|
+
// gjsify generate-installer
|
|
14
|
+
// git add install.mjs && git commit
|
|
15
|
+
// # README:
|
|
16
|
+
// # curl -fsSL https://github.com/me/my-gjs-app/raw/main/install.mjs \
|
|
17
|
+
// # -o /tmp/i.mjs && gjs -m /tmp/i.mjs && rm /tmp/i.mjs
|
|
18
|
+
//
|
|
19
|
+
// The template is read at build time (static-read-inliner inlines the file
|
|
20
|
+
// contents into the bundled CLI), so the runtime cost is just a string
|
|
21
|
+
// replace + write.
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { resolve } from 'node:path';
|
|
24
|
+
// Lazy load. Reading at the top level breaks `gjsify run copy-templates`
|
|
25
|
+
// (the bootstrap step that ships the template into `lib/templates/` after
|
|
26
|
+
// `tsc`): the run script must first import this module to dispatch into
|
|
27
|
+
// itself, which would then ENOENT on the not-yet-copied template file.
|
|
28
|
+
// The static-read-inliner can still detect this shape inside the handler.
|
|
29
|
+
function loadInstallerTemplate() {
|
|
30
|
+
return readFileSync(new URL('../templates/install.mjs.tmpl', import.meta.url), 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
const DEFAULT_BOOTSTRAP_URL = 'https://github.com/gjsify/gjsify/releases/latest/download/cli.gjs.mjs';
|
|
33
|
+
export const generateInstallerCommand = {
|
|
34
|
+
command: 'generate-installer [target]',
|
|
35
|
+
description: 'Scaffold an install.mjs in the current directory for a GJS-runnable npm package.',
|
|
36
|
+
builder: (yargs) => yargs
|
|
37
|
+
.positional('target', {
|
|
38
|
+
description: 'Npm package name to install (default: current package.json name).',
|
|
39
|
+
type: 'string',
|
|
40
|
+
})
|
|
41
|
+
.option('bin-name', {
|
|
42
|
+
description: 'Bin name produced by the installer (default: first key of `gjsify.bin` or `bin`).',
|
|
43
|
+
type: 'string',
|
|
44
|
+
})
|
|
45
|
+
.option('bootstrap-url', {
|
|
46
|
+
description: 'Override the cli.gjs.mjs bootstrap bundle URL (default: gjsify GitHub releases/latest).',
|
|
47
|
+
type: 'string',
|
|
48
|
+
})
|
|
49
|
+
.option('output', {
|
|
50
|
+
description: 'Where to write the generated installer.',
|
|
51
|
+
type: 'string',
|
|
52
|
+
default: 'install.mjs',
|
|
53
|
+
})
|
|
54
|
+
.option('force', {
|
|
55
|
+
description: 'Overwrite an existing output file.',
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
default: false,
|
|
58
|
+
}),
|
|
59
|
+
handler: (args) => {
|
|
60
|
+
const outputPath = resolve(process.cwd(), args.output);
|
|
61
|
+
if (existsSync(outputPath) && !args.force) {
|
|
62
|
+
console.error(`${args.output} already exists. Re-run with --force to overwrite.`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const pkgJsonPath = resolve(process.cwd(), 'package.json');
|
|
67
|
+
let pkgJson = null;
|
|
68
|
+
if (existsSync(pkgJsonPath)) {
|
|
69
|
+
try {
|
|
70
|
+
pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* no pkg.json or unparsable — fall back to flags */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const target = args.target ?? pkgJson?.name;
|
|
77
|
+
if (!target) {
|
|
78
|
+
console.error('No target package: pass `gjsify generate-installer <pkg>` or run inside a directory with a package.json.');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const binName = args['bin-name'] ?? pickDefaultBinName(pkgJson, target);
|
|
83
|
+
const bootstrapUrl = args['bootstrap-url'] ?? DEFAULT_BOOTSTRAP_URL;
|
|
84
|
+
const rendered = loadInstallerTemplate()
|
|
85
|
+
.replace(/const DEFAULT_TARGET = '[^']+';/, `const DEFAULT_TARGET = ${JSON.stringify(target)};`)
|
|
86
|
+
.replace(/const DEFAULT_BIN_NAME = '[^']+';/, `const DEFAULT_BIN_NAME = ${JSON.stringify(binName)};`)
|
|
87
|
+
.replace(/const DEFAULT_BOOTSTRAP_URL =\s*'[^']+';/, `const DEFAULT_BOOTSTRAP_URL = ${JSON.stringify(bootstrapUrl)};`);
|
|
88
|
+
writeFileSync(outputPath, rendered, { mode: 0o755 });
|
|
89
|
+
console.log(`Wrote ${args.output} (target=${target}, bin=${binName}).`);
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log('Install one-liner for your README:');
|
|
92
|
+
console.log(` curl -fsSL https://github.com/<you>/<repo>/raw/main/${args.output} -o /tmp/i.mjs \\`);
|
|
93
|
+
console.log(' && gjs -m /tmp/i.mjs && rm /tmp/i.mjs');
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
function pickDefaultBinName(pkgJson, target) {
|
|
97
|
+
const gjsifyBin = pkgJson?.gjsify?.bin;
|
|
98
|
+
if (gjsifyBin && typeof gjsifyBin === 'object') {
|
|
99
|
+
const first = Object.keys(gjsifyBin)[0];
|
|
100
|
+
if (first)
|
|
101
|
+
return first;
|
|
102
|
+
}
|
|
103
|
+
const npmBin = pkgJson?.bin;
|
|
104
|
+
if (npmBin && typeof npmBin === 'object') {
|
|
105
|
+
const first = Object.keys(npmBin)[0];
|
|
106
|
+
if (first)
|
|
107
|
+
return first;
|
|
108
|
+
}
|
|
109
|
+
if (typeof npmBin === 'string') {
|
|
110
|
+
return target.startsWith('@') ? target.slice(target.indexOf('/') + 1) : target;
|
|
111
|
+
}
|
|
112
|
+
return target.startsWith('@') ? target.slice(target.indexOf('/') + 1) : target;
|
|
113
|
+
}
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -12,3 +12,7 @@ export * from './dlx.js';
|
|
|
12
12
|
export * from './install.js';
|
|
13
13
|
export * from './foreach.js';
|
|
14
14
|
export * from './workspace.js';
|
|
15
|
+
export * from './pack.js';
|
|
16
|
+
export * from './publish.js';
|
|
17
|
+
export * from './self-update.js';
|
|
18
|
+
export * from './generate-installer.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -12,3 +12,7 @@ export * from './dlx.js';
|
|
|
12
12
|
export * from './install.js';
|
|
13
13
|
export * from './foreach.js';
|
|
14
14
|
export * from './workspace.js';
|
|
15
|
+
export * from './pack.js';
|
|
16
|
+
export * from './publish.js';
|
|
17
|
+
export * from './self-update.js';
|
|
18
|
+
export * from './generate-installer.js';
|
|
@@ -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 {};
|