bc-pkg 1.0.1 → 1.0.2
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/README.md +30 -0
- package/bin/bc-pkg.js +286 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,36 @@ npx bc-pkg bigconfig-ai/once@2f4e8c0d0b4c4b8f0c3a9f6e2a1b5c7d8e9f0123 package va
|
|
|
24
24
|
On the first run, `bc-pkg` resolves the ref to a full SHA and pins it. Later
|
|
25
25
|
runs omit `<owner/repo@ref>` and keep using the pinned SHA.
|
|
26
26
|
|
|
27
|
+
## Local repositories
|
|
28
|
+
|
|
29
|
+
For live local development you can point `bc-pkg` at a local checkout of a
|
|
30
|
+
target package instead of a GitHub spec. The first argument is treated as a
|
|
31
|
+
local path when it starts with `/`, `./`, `../`, `~`, or is `.`/`..`:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npx bc-pkg ../once/typescript package build
|
|
35
|
+
npx bc-pkg /abs/path/to/once/clojure package build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Local targets are wired up **live** — your uncommitted edits in the local
|
|
39
|
+
package are picked up on the next run, with no SHA pinning and no push:
|
|
40
|
+
|
|
41
|
+
| Target language | Local dependency |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Clojure | `deps.edn` / `bb.edn` use `:local/root` |
|
|
44
|
+
| TypeScript | `package.json` uses a `file:` dependency |
|
|
45
|
+
| Python | `pyproject.toml` uses an editable `[tool.uv.sources]` path |
|
|
46
|
+
|
|
47
|
+
The `run` file is symlinked (not copied) so run-file edits are also live. Run
|
|
48
|
+
`bc-pkg` from a **separate** directory; pointing it at the current directory is
|
|
49
|
+
refused so it never overwrites the package's own manifest. Switching an
|
|
50
|
+
initialized directory between local and GitHub (or to a different local path) is
|
|
51
|
+
a hard error, just like a repo/ref/SHA mismatch.
|
|
52
|
+
|
|
53
|
+
Notes: TypeScript local dev requires the local package to be built (its
|
|
54
|
+
`dist/`); Python local dev installs the package editable and exposes its
|
|
55
|
+
`resources/` from the source tree.
|
|
56
|
+
|
|
27
57
|
## What is created
|
|
28
58
|
|
|
29
59
|
The launcher copies the target package's root `run` file into the current
|
package/bin/bc-pkg.js
CHANGED
|
@@ -27,7 +27,7 @@ function fail(msg) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function usage() {
|
|
30
|
-
return `Usage:\n bc-pkg <owner/repo@ref> <args...>\n bc-pkg <args...>\n\nExamples:\n npx bc-pkg bigconfig-ai/once@typescript package validate\n npx bc-pkg package validate`;
|
|
30
|
+
return `Usage:\n bc-pkg <owner/repo@ref> <args...>\n bc-pkg <local-path> <args...>\n bc-pkg <args...>\n\nExamples:\n npx bc-pkg bigconfig-ai/once@typescript package validate\n npx bc-pkg ../once/typescript package validate\n npx bc-pkg package validate`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// --- generic process helpers -------------------------------------------
|
|
@@ -90,6 +90,37 @@ function parseSpec(arg) {
|
|
|
90
90
|
return { owner: m[1], repo: m[2], ref: m[3], slug: `${m[1]}/${m[2]}` };
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// A normal local path: leading slash, dot-slash, or tilde. Bare names with no
|
|
94
|
+
// path prefix are treated as forwarded args, not local targets.
|
|
95
|
+
function isLocalSpec(arg) {
|
|
96
|
+
if (!arg) return false;
|
|
97
|
+
if (arg === '.' || arg === '..') return true;
|
|
98
|
+
if (arg.startsWith('/') || arg.startsWith('~') || arg.startsWith('./') || arg.startsWith('../')) return true;
|
|
99
|
+
if (process.platform === 'win32') {
|
|
100
|
+
if (arg.startsWith('.\\') || arg.startsWith('..\\') || arg.startsWith('\\')) return true;
|
|
101
|
+
if (/^[A-Za-z]:[\\/]/.test(arg)) return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseLocalSpec(arg) {
|
|
107
|
+
let p = arg;
|
|
108
|
+
if (p === '~' || p.startsWith('~/') || p.startsWith('~\\')) {
|
|
109
|
+
p = path.join(os.homedir(), p.slice(1));
|
|
110
|
+
}
|
|
111
|
+
const abs = path.resolve(process.cwd(), p);
|
|
112
|
+
let resolved;
|
|
113
|
+
try {
|
|
114
|
+
resolved = fs.realpathSync(abs);
|
|
115
|
+
} catch {
|
|
116
|
+
fail(`local path ${JSON.stringify(arg)} is not a directory (${abs})`);
|
|
117
|
+
}
|
|
118
|
+
if (!fs.statSync(resolved).isDirectory()) {
|
|
119
|
+
fail(`local path ${JSON.stringify(arg)} is not a directory (${resolved})`);
|
|
120
|
+
}
|
|
121
|
+
return { path: resolved, name: path.basename(resolved) };
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
function ghHeaders(accept) {
|
|
94
125
|
const headers = {
|
|
95
126
|
'user-agent': 'bc-pkg',
|
|
@@ -160,35 +191,63 @@ function sectionText(text, name) {
|
|
|
160
191
|
return next ? rest.slice(0, next.index) : rest;
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
|
|
164
|
-
const [depsEdn, packageJsonText, pyprojectText] = await Promise.all([
|
|
165
|
-
fetchFile(spec, sha, 'deps.edn'),
|
|
166
|
-
fetchFile(spec, sha, 'package.json'),
|
|
167
|
-
fetchFile(spec, sha, 'pyproject.toml'),
|
|
168
|
-
]);
|
|
169
|
-
|
|
194
|
+
function detectTargetFromManifests(label, depsEdn, packageJsonText, pyprojectText, clojureCoord) {
|
|
170
195
|
const found = [];
|
|
171
196
|
if (depsEdn != null) found.push('clojure');
|
|
172
197
|
if (packageJsonText != null) found.push('typescript');
|
|
173
198
|
if (pyprojectText != null) found.push('python');
|
|
174
199
|
if (found.length === 0) {
|
|
175
|
-
fail(`${
|
|
200
|
+
fail(`${label} has no deps.edn, package.json, or pyproject.toml`);
|
|
176
201
|
}
|
|
177
202
|
if (found.length > 1) {
|
|
178
|
-
fail(`${
|
|
203
|
+
fail(`${label} is ambiguous; found ${found.join(', ')} manifests`);
|
|
179
204
|
}
|
|
180
205
|
|
|
181
206
|
if (found[0] === 'typescript') {
|
|
182
207
|
const pkg = parseJson(packageJsonText, 'package.json');
|
|
183
|
-
if (!pkg.name) fail(`${
|
|
208
|
+
if (!pkg.name) fail(`${label} package.json has no name`);
|
|
184
209
|
return { language: 'typescript', packageName: pkg.name };
|
|
185
210
|
}
|
|
186
211
|
if (found[0] === 'python') {
|
|
187
212
|
const packageName = parsePyProjectName(pyprojectText);
|
|
188
|
-
if (!packageName) fail(`${
|
|
213
|
+
if (!packageName) fail(`${label} pyproject.toml has no [project].name`);
|
|
189
214
|
return { language: 'python', packageName };
|
|
190
215
|
}
|
|
191
|
-
return { language: 'clojure', packageName:
|
|
216
|
+
return { language: 'clojure', packageName: clojureCoord };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function detectTarget(spec, sha) {
|
|
220
|
+
const [depsEdn, packageJsonText, pyprojectText] = await Promise.all([
|
|
221
|
+
fetchFile(spec, sha, 'deps.edn'),
|
|
222
|
+
fetchFile(spec, sha, 'package.json'),
|
|
223
|
+
fetchFile(spec, sha, 'pyproject.toml'),
|
|
224
|
+
]);
|
|
225
|
+
return detectTargetFromManifests(
|
|
226
|
+
`${spec.slug}@${sha.slice(0, 7)}`,
|
|
227
|
+
depsEdn,
|
|
228
|
+
packageJsonText,
|
|
229
|
+
pyprojectText,
|
|
230
|
+
`io.github.${spec.owner}/${spec.repo}`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function readLocalFile(local, filePath, { required = false } = {}) {
|
|
235
|
+
const f = path.join(local.path, filePath);
|
|
236
|
+
if (!fs.existsSync(f) || !fs.statSync(f).isFile()) {
|
|
237
|
+
if (required) fail(`${local.path} has no ${filePath}`);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return fs.readFileSync(f, 'utf8');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function detectTargetLocal(local) {
|
|
244
|
+
return detectTargetFromManifests(
|
|
245
|
+
local.path,
|
|
246
|
+
readLocalFile(local, 'deps.edn'),
|
|
247
|
+
readLocalFile(local, 'package.json'),
|
|
248
|
+
readLocalFile(local, 'pyproject.toml'),
|
|
249
|
+
`local/${local.name}`,
|
|
250
|
+
);
|
|
192
251
|
}
|
|
193
252
|
|
|
194
253
|
// --- native metadata -----------------------------------------------------
|
|
@@ -216,6 +275,8 @@ function metadataFromPyproject(file) {
|
|
|
216
275
|
language: get('language') || 'python',
|
|
217
276
|
run: get('run') || 'run',
|
|
218
277
|
packageName: get('package-name'),
|
|
278
|
+
local: /^\s*local\s*=\s*true\s*$/m.test(sec),
|
|
279
|
+
path: get('path'),
|
|
219
280
|
manifest: file,
|
|
220
281
|
};
|
|
221
282
|
}
|
|
@@ -223,7 +284,7 @@ function metadataFromPyproject(file) {
|
|
|
223
284
|
function metadataFromDepsEdn(file) {
|
|
224
285
|
if (!fs.existsSync(file)) return null;
|
|
225
286
|
const text = fs.readFileSync(file, 'utf8');
|
|
226
|
-
if (!text.includes(':bigconfig/repo')) return null;
|
|
287
|
+
if (!text.includes(':bigconfig/repo') && !text.includes(':bigconfig/path')) return null;
|
|
227
288
|
const get = (key) => {
|
|
228
289
|
const m = text.match(new RegExp(`:${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s+"([^"]+)"`));
|
|
229
290
|
return m ? m[1] : undefined;
|
|
@@ -234,6 +295,8 @@ function metadataFromDepsEdn(file) {
|
|
|
234
295
|
sha: get('bigconfig/sha'),
|
|
235
296
|
language: get('bigconfig/language') || 'clojure',
|
|
236
297
|
run: get('bigconfig/run') || 'run',
|
|
298
|
+
local: get('bigconfig/local') === 'true',
|
|
299
|
+
path: get('bigconfig/path'),
|
|
237
300
|
manifest: file,
|
|
238
301
|
};
|
|
239
302
|
}
|
|
@@ -249,13 +312,18 @@ function readMetadata(cwd = process.cwd()) {
|
|
|
249
312
|
}
|
|
250
313
|
if (metas.length === 0) return null;
|
|
251
314
|
const meta = metas[0];
|
|
252
|
-
if (
|
|
315
|
+
if (meta.local) {
|
|
316
|
+
if (!meta.path || !meta.language) fail(`Incomplete BigConfig metadata in ${meta.manifest}`);
|
|
317
|
+
} else if (!meta.repo || !meta.ref || !meta.sha || !meta.language) {
|
|
253
318
|
fail(`Incomplete BigConfig metadata in ${meta.manifest}`);
|
|
254
319
|
}
|
|
255
320
|
return meta;
|
|
256
321
|
}
|
|
257
322
|
|
|
258
323
|
function validateExistingMetadata(meta, spec, sha) {
|
|
324
|
+
if (meta.local) {
|
|
325
|
+
fail('Current directory is initialized for a local BigConfig package; refusing to switch to a GitHub package.');
|
|
326
|
+
}
|
|
259
327
|
const expectedRepo = spec.slug;
|
|
260
328
|
const problems = [];
|
|
261
329
|
if (meta.repo !== expectedRepo) problems.push(`repo ${JSON.stringify(meta.repo)} != ${JSON.stringify(expectedRepo)}`);
|
|
@@ -266,6 +334,23 @@ function validateExistingMetadata(meta, spec, sha) {
|
|
|
266
334
|
}
|
|
267
335
|
}
|
|
268
336
|
|
|
337
|
+
function validateExistingLocalMetadata(meta, local) {
|
|
338
|
+
if (!meta.local) {
|
|
339
|
+
fail('Current directory is initialized for a GitHub BigConfig package; refusing to switch to a local package.');
|
|
340
|
+
}
|
|
341
|
+
let existing = null;
|
|
342
|
+
if (meta.path) {
|
|
343
|
+
try {
|
|
344
|
+
existing = fs.realpathSync(meta.path);
|
|
345
|
+
} catch {
|
|
346
|
+
existing = path.resolve(meta.path);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (existing !== local.path) {
|
|
350
|
+
fail(`Current directory is already initialized for a different local BigConfig package:\n path ${JSON.stringify(meta.path)} != ${JSON.stringify(local.path)}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
269
354
|
function quoteToml(s) {
|
|
270
355
|
return JSON.stringify(String(s));
|
|
271
356
|
}
|
|
@@ -276,6 +361,24 @@ function writeRunFile(text) {
|
|
|
276
361
|
if (process.platform !== 'win32') fs.chmodSync(target, 0o755);
|
|
277
362
|
}
|
|
278
363
|
|
|
364
|
+
// Local targets symlink the run file so edits to the local package flow through;
|
|
365
|
+
// copy as a fallback where symlinks are unavailable.
|
|
366
|
+
function linkRunFile(source) {
|
|
367
|
+
const target = path.join(process.cwd(), 'run');
|
|
368
|
+
try {
|
|
369
|
+
fs.lstatSync(target);
|
|
370
|
+
fs.rmSync(target, { force: true });
|
|
371
|
+
} catch {
|
|
372
|
+
// nothing to remove
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
fs.symlinkSync(source, target);
|
|
376
|
+
} catch {
|
|
377
|
+
fs.copyFileSync(source, target);
|
|
378
|
+
if (process.platform !== 'win32') fs.chmodSync(target, 0o755);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
279
382
|
function clojureCoord(spec) {
|
|
280
383
|
return `io.github.${spec.owner}/${spec.repo}`;
|
|
281
384
|
}
|
|
@@ -335,8 +438,133 @@ function writeNativeManifest(spec, sha, target) {
|
|
|
335
438
|
fail(`Unsupported language: ${target.language}`);
|
|
336
439
|
}
|
|
337
440
|
|
|
441
|
+
// --- local (live) manifests ----------------------------------------------
|
|
442
|
+
|
|
443
|
+
function writeClojureManifestLocal(local, target) {
|
|
444
|
+
const coord = target.packageName || `local/${local.name}`;
|
|
445
|
+
const p = local.path;
|
|
446
|
+
const deps = `{:deps {${coord} {:local/root "${p}"}}\n :bigconfig/local "true"\n :bigconfig/path "${p}"\n :bigconfig/language "clojure"\n :bigconfig/run "run"}\n`;
|
|
447
|
+
fs.writeFileSync(path.join(process.cwd(), 'deps.edn'), deps);
|
|
448
|
+
|
|
449
|
+
// Babashka reads bb.edn; metadata stays in deps.edn per the launcher contract.
|
|
450
|
+
const bb = `{:deps {${coord} {:local/root "${p}"}}}\n`;
|
|
451
|
+
fs.writeFileSync(path.join(process.cwd(), 'bb.edn'), bb);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function writeTypeScriptManifestLocal(local, target) {
|
|
455
|
+
const file = path.join(process.cwd(), 'package.json');
|
|
456
|
+
let pkg = {};
|
|
457
|
+
if (fs.existsSync(file)) {
|
|
458
|
+
pkg = parseJson(fs.readFileSync(file, 'utf8'), file);
|
|
459
|
+
if (pkg.bigconfig) validateExistingLocalMetadata(metadataFromPackageJson(file), local);
|
|
460
|
+
}
|
|
461
|
+
const packageName = target.packageName || local.name;
|
|
462
|
+
pkg.type = pkg.type || 'module';
|
|
463
|
+
pkg.scripts = { ...(pkg.scripts || {}), run: 'node run' };
|
|
464
|
+
pkg.dependencies = { ...(pkg.dependencies || {}) };
|
|
465
|
+
pkg.dependencies[packageName] = `file:${local.path}`;
|
|
466
|
+
pkg.bigconfig = {
|
|
467
|
+
local: true,
|
|
468
|
+
path: local.path,
|
|
469
|
+
language: 'typescript',
|
|
470
|
+
run: 'run',
|
|
471
|
+
packageName,
|
|
472
|
+
};
|
|
473
|
+
fs.writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function writePythonManifestLocal(local, target) {
|
|
477
|
+
const file = path.join(process.cwd(), 'pyproject.toml');
|
|
478
|
+
if (fs.existsSync(file)) {
|
|
479
|
+
const existing = metadataFromPyproject(file);
|
|
480
|
+
if (!existing) {
|
|
481
|
+
fail('pyproject.toml already exists and is not initialized for bc-pkg; refusing to rewrite it.');
|
|
482
|
+
}
|
|
483
|
+
validateExistingLocalMetadata(existing, local);
|
|
484
|
+
}
|
|
485
|
+
const packageName = target.packageName || local.name;
|
|
486
|
+
const text = `[project]\nname = "bigconfig-cli"\nversion = "0.1.0"\nrequires-python = ">=3.12"\ndependencies = [\n ${quoteToml(packageName)},\n]\n\n[tool.uv.sources]\n${quoteToml(packageName)} = { path = ${quoteToml(local.path)}, editable = true }\n\n[tool.bigconfig]\nlocal = true\npath = ${quoteToml(local.path)}\nlanguage = "python"\nrun = "run"\npackage-name = ${quoteToml(packageName)}\n`;
|
|
487
|
+
fs.writeFileSync(file, text);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function writeNativeManifestLocal(local, target) {
|
|
491
|
+
if (target.language === 'clojure') return writeClojureManifestLocal(local, target);
|
|
492
|
+
if (target.language === 'typescript') return writeTypeScriptManifestLocal(local, target);
|
|
493
|
+
if (target.language === 'python') return writePythonManifestLocal(local, target);
|
|
494
|
+
fail(`Unsupported language: ${target.language}`);
|
|
495
|
+
}
|
|
496
|
+
|
|
338
497
|
// --- target dependency setup and execution -------------------------------
|
|
339
498
|
|
|
499
|
+
function pythonSitePackagesDirs() {
|
|
500
|
+
const venv = path.join(process.cwd(), '.venv');
|
|
501
|
+
const candidates = [path.join(venv, 'Lib', 'site-packages')];
|
|
502
|
+
for (const libName of ['lib', 'lib64']) {
|
|
503
|
+
const libDir = path.join(venv, libName);
|
|
504
|
+
let entries = [];
|
|
505
|
+
try {
|
|
506
|
+
entries = fs.readdirSync(libDir);
|
|
507
|
+
} catch {
|
|
508
|
+
entries = [];
|
|
509
|
+
}
|
|
510
|
+
for (const e of entries.sort()) {
|
|
511
|
+
if (e.startsWith('python')) candidates.push(path.join(libDir, e, 'site-packages'));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const seen = new Set();
|
|
515
|
+
const result = [];
|
|
516
|
+
for (const c of candidates) {
|
|
517
|
+
if (seen.has(c)) continue;
|
|
518
|
+
seen.add(c);
|
|
519
|
+
try {
|
|
520
|
+
if (fs.statSync(c).isDirectory()) result.push(c);
|
|
521
|
+
} catch {
|
|
522
|
+
// not present
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Python wheels install templates as top-level package data under
|
|
529
|
+
// site-packages/resources; BigConfig's renderer resolves them from ./resources.
|
|
530
|
+
// For editable local installs the source tree is used instead (resources live
|
|
531
|
+
// under <path>/src/resources or <path>/resources, never in site-packages).
|
|
532
|
+
function exposePythonResources(meta) {
|
|
533
|
+
const target = path.join(process.cwd(), 'resources');
|
|
534
|
+
try {
|
|
535
|
+
if (fs.statSync(target)) return;
|
|
536
|
+
} catch {
|
|
537
|
+
// not present; continue
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
if (fs.lstatSync(target)) fs.rmSync(target, { force: true, recursive: true });
|
|
541
|
+
} catch {
|
|
542
|
+
// nothing to remove (e.g. broken symlink already gone)
|
|
543
|
+
}
|
|
544
|
+
const candidates = [];
|
|
545
|
+
if (meta && meta.local && meta.path) {
|
|
546
|
+
candidates.push(path.join(meta.path, 'src', 'resources'), path.join(meta.path, 'resources'));
|
|
547
|
+
}
|
|
548
|
+
for (const site of pythonSitePackagesDirs()) candidates.push(path.join(site, 'resources'));
|
|
549
|
+
let source = null;
|
|
550
|
+
for (const c of candidates) {
|
|
551
|
+
try {
|
|
552
|
+
if (fs.statSync(c).isDirectory()) {
|
|
553
|
+
source = c;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
} catch {
|
|
557
|
+
// not a dir
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (!source) return;
|
|
561
|
+
try {
|
|
562
|
+
fs.symlinkSync(source, target, 'dir');
|
|
563
|
+
} catch {
|
|
564
|
+
fs.cpSync(source, target, { recursive: true });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
340
568
|
async function ensureTargetDeps(meta) {
|
|
341
569
|
if (meta.language === 'typescript') {
|
|
342
570
|
requireCommand('node', 'Install Node.js and try again.');
|
|
@@ -357,6 +585,7 @@ async function ensureTargetDeps(meta) {
|
|
|
357
585
|
const code = await runCommand('uv', ['sync']);
|
|
358
586
|
if (code !== 0) process.exit(code);
|
|
359
587
|
}
|
|
588
|
+
exposePythonResources(meta);
|
|
360
589
|
return;
|
|
361
590
|
}
|
|
362
591
|
}
|
|
@@ -395,9 +624,36 @@ async function initialize(spec, sha) {
|
|
|
395
624
|
};
|
|
396
625
|
}
|
|
397
626
|
|
|
627
|
+
function initializeLocal(local) {
|
|
628
|
+
if (local.path === fs.realpathSync(process.cwd())) {
|
|
629
|
+
fail("local path is the current directory; run bc-pkg from a separate directory so it does not overwrite the package's own manifest.");
|
|
630
|
+
}
|
|
631
|
+
const target = detectTargetLocal(local);
|
|
632
|
+
const runSource = path.join(local.path, 'run');
|
|
633
|
+
if (!fs.existsSync(runSource) || !fs.statSync(runSource).isFile()) fail(`${local.path} has no run`);
|
|
634
|
+
linkRunFile(runSource);
|
|
635
|
+
writeNativeManifestLocal(local, target);
|
|
636
|
+
return {
|
|
637
|
+
repo: null,
|
|
638
|
+
ref: null,
|
|
639
|
+
sha: null,
|
|
640
|
+
language: target.language,
|
|
641
|
+
run: 'run',
|
|
642
|
+
packageName: target.packageName,
|
|
643
|
+
local: true,
|
|
644
|
+
path: local.path,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
398
648
|
async function restoreRunIfMissing(meta) {
|
|
399
649
|
const runPath = path.join(process.cwd(), meta.run || 'run');
|
|
400
650
|
if (fs.existsSync(runPath)) return;
|
|
651
|
+
if (meta.local) {
|
|
652
|
+
const source = path.join(meta.path, meta.run || 'run');
|
|
653
|
+
if (!fs.existsSync(source)) fail(`${meta.path} has no ${meta.run || 'run'}`);
|
|
654
|
+
linkRunFile(source);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
401
657
|
const [owner, repo] = meta.repo.split('/');
|
|
402
658
|
const spec = { owner, repo, slug: meta.repo, ref: meta.ref };
|
|
403
659
|
const runText = await fetchFile(spec, meta.sha, 'run', { required: true });
|
|
@@ -652,11 +908,19 @@ function runBb(bbPath, args, javaHome, extraEnv = {}) {
|
|
|
652
908
|
|
|
653
909
|
async function main(argv) {
|
|
654
910
|
let args = [...argv];
|
|
655
|
-
|
|
656
|
-
|
|
911
|
+
const first = args.length ? args[0] : null;
|
|
912
|
+
const localSpec = first != null && isLocalSpec(first) ? parseLocalSpec(first) : null;
|
|
913
|
+
const spec = localSpec ? null : first ? parseSpec(first) : null;
|
|
914
|
+
if (localSpec || spec) args = args.slice(1);
|
|
657
915
|
|
|
658
916
|
let meta = readMetadata();
|
|
659
|
-
if (
|
|
917
|
+
if (localSpec) {
|
|
918
|
+
if (meta) {
|
|
919
|
+
validateExistingLocalMetadata(meta, localSpec);
|
|
920
|
+
} else {
|
|
921
|
+
meta = initializeLocal(localSpec);
|
|
922
|
+
}
|
|
923
|
+
} else if (spec) {
|
|
660
924
|
const sha = await resolveRef(spec);
|
|
661
925
|
if (meta) {
|
|
662
926
|
validateExistingMetadata(meta, spec, sha);
|
|
@@ -681,10 +945,14 @@ if (require.main === module) {
|
|
|
681
945
|
|
|
682
946
|
module.exports = {
|
|
683
947
|
parseSpec,
|
|
948
|
+
isLocalSpec,
|
|
949
|
+
parseLocalSpec,
|
|
684
950
|
resolveRef,
|
|
685
951
|
detectTarget,
|
|
952
|
+
detectTargetLocal,
|
|
686
953
|
readMetadata,
|
|
687
954
|
validateExistingMetadata,
|
|
955
|
+
validateExistingLocalMetadata,
|
|
688
956
|
resolvePlatform,
|
|
689
957
|
cacheRoot,
|
|
690
958
|
findJavaHome,
|