@ozyman42/ozy-cli 0.0.4 → 0.1.0
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/index.js +435 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2784,7 +2784,7 @@ var require_ssh_config2 = __commonJS((exports) => {
|
|
|
2784
2784
|
exports.default = ssh_config_1.default;
|
|
2785
2785
|
});
|
|
2786
2786
|
// package.json
|
|
2787
|
-
var version = "0.0
|
|
2787
|
+
var version = "0.1.0";
|
|
2788
2788
|
|
|
2789
2789
|
// node_modules/commander/esm.mjs
|
|
2790
2790
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -2815,9 +2815,13 @@ function log(...stuff) {
|
|
|
2815
2815
|
// src/common/command.ts
|
|
2816
2816
|
function makeCommand(name, description, action) {
|
|
2817
2817
|
return new Command(name).description(description).action(async () => {
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2818
|
+
try {
|
|
2819
|
+
const result = await action();
|
|
2820
|
+
if (!result.success) {
|
|
2821
|
+
log(`\u2717 ${result.error}: ${result.reason}`);
|
|
2822
|
+
}
|
|
2823
|
+
} catch (err) {
|
|
2824
|
+
log(`\u2717 unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2821
2825
|
}
|
|
2822
2826
|
process.exit();
|
|
2823
2827
|
});
|
|
@@ -8446,9 +8450,435 @@ var git = new Command("git").summary("setup git in repo for verified commits");
|
|
|
8446
8450
|
git.addCommand(cmd);
|
|
8447
8451
|
});
|
|
8448
8452
|
|
|
8453
|
+
// src/commands/npm/setup/index.ts
|
|
8454
|
+
var {$: $6 } = globalThis.Bun;
|
|
8455
|
+
import { existsSync as existsSync4 } from "fs";
|
|
8456
|
+
|
|
8457
|
+
// src/commands/npm/setup/npm-auth.ts
|
|
8458
|
+
var {$: $3 } = globalThis.Bun;
|
|
8459
|
+
import { readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
8460
|
+
import { homedir } from "os";
|
|
8461
|
+
var npmrcPath = `${homedir()}/.npmrc`;
|
|
8462
|
+
var TOKEN_RE = /^\/\/registry\.npmjs\.org\/:_authToken=(\S+)$/m;
|
|
8463
|
+
function readNpmrc() {
|
|
8464
|
+
try {
|
|
8465
|
+
return readFileSync(npmrcPath, "utf8");
|
|
8466
|
+
} catch {
|
|
8467
|
+
return "";
|
|
8468
|
+
}
|
|
8469
|
+
}
|
|
8470
|
+
function stripTokenFromDisk() {
|
|
8471
|
+
const content = readNpmrc();
|
|
8472
|
+
const stripped = content.replace(TOKEN_RE, "").replace(/\n{3,}/g, `
|
|
8473
|
+
|
|
8474
|
+
`).trim();
|
|
8475
|
+
if (!stripped) {
|
|
8476
|
+
try {
|
|
8477
|
+
unlinkSync(npmrcPath);
|
|
8478
|
+
} catch {}
|
|
8479
|
+
} else {
|
|
8480
|
+
writeFileSync(npmrcPath, stripped + `
|
|
8481
|
+
`);
|
|
8482
|
+
}
|
|
8483
|
+
}
|
|
8484
|
+
async function whoamiWithToken(token) {
|
|
8485
|
+
const result = await $3`npm whoami`.env({ ...process.env, NODE_AUTH_TOKEN: token }).quiet().nothrow();
|
|
8486
|
+
if (result.exitCode !== 0)
|
|
8487
|
+
return null;
|
|
8488
|
+
return result.stdout.toString().trim();
|
|
8489
|
+
}
|
|
8490
|
+
async function acquireToken() {
|
|
8491
|
+
const existing = readNpmrc().match(TOKEN_RE)?.[1];
|
|
8492
|
+
if (existing) {
|
|
8493
|
+
const user2 = await whoamiWithToken(existing);
|
|
8494
|
+
if (user2) {
|
|
8495
|
+
stripTokenFromDisk();
|
|
8496
|
+
return { token: existing, user: user2 };
|
|
8497
|
+
}
|
|
8498
|
+
}
|
|
8499
|
+
log(" not logged in \u2014 opening npm login...");
|
|
8500
|
+
await $3`npm login`.nothrow();
|
|
8501
|
+
const token = readNpmrc().match(TOKEN_RE)?.[1];
|
|
8502
|
+
if (!token)
|
|
8503
|
+
throw new Error('npm login did not produce a token \u2014 try running "npm login" manually');
|
|
8504
|
+
const user = await whoamiWithToken(token);
|
|
8505
|
+
if (!user)
|
|
8506
|
+
throw new Error("npm login succeeded but token verification failed");
|
|
8507
|
+
stripTokenFromDisk();
|
|
8508
|
+
return { token, user };
|
|
8509
|
+
}
|
|
8510
|
+
|
|
8511
|
+
// src/commands/npm/setup/package-registry.ts
|
|
8512
|
+
async function fetchPackageInfo(name) {
|
|
8513
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}`);
|
|
8514
|
+
if (res.status === 404)
|
|
8515
|
+
return null;
|
|
8516
|
+
if (!res.ok)
|
|
8517
|
+
throw new Error(`registry error ${res.status} for ${name}`);
|
|
8518
|
+
const data = await res.json();
|
|
8519
|
+
return {
|
|
8520
|
+
name: data.name,
|
|
8521
|
+
latestVersion: data["dist-tags"]?.latest,
|
|
8522
|
+
distTags: data["dist-tags"] ?? {}
|
|
8523
|
+
};
|
|
8524
|
+
}
|
|
8525
|
+
|
|
8526
|
+
// src/commands/npm/setup/trusted-publishing.ts
|
|
8527
|
+
var {$: $4 } = globalThis.Bun;
|
|
8528
|
+
import { createInterface } from "readline";
|
|
8529
|
+
function encodePackageName(name) {
|
|
8530
|
+
if (!name.startsWith("@"))
|
|
8531
|
+
return encodeURIComponent(name);
|
|
8532
|
+
const slash = name.indexOf("/");
|
|
8533
|
+
return `@${name.slice(1, slash)}%2F${name.slice(slash + 1)}`;
|
|
8534
|
+
}
|
|
8535
|
+
function promptPasscode() {
|
|
8536
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8537
|
+
return new Promise((resolve5) => {
|
|
8538
|
+
rl.question(" enter the passcode from the browser: ", (answer) => {
|
|
8539
|
+
rl.close();
|
|
8540
|
+
resolve5(answer.trim());
|
|
8541
|
+
});
|
|
8542
|
+
});
|
|
8543
|
+
}
|
|
8544
|
+
async function registryPost(url, body, authToken, { existingOtp, alreadyExistsStatus } = {}) {
|
|
8545
|
+
const headers = {
|
|
8546
|
+
Authorization: `Bearer ${authToken}`,
|
|
8547
|
+
"Content-Type": "application/json"
|
|
8548
|
+
};
|
|
8549
|
+
const post = (otp) => fetch(url, {
|
|
8550
|
+
method: "POST",
|
|
8551
|
+
headers: otp ? { ...headers, "npm-otp": otp } : headers,
|
|
8552
|
+
body
|
|
8553
|
+
});
|
|
8554
|
+
const check = (res2) => {
|
|
8555
|
+
if (res2.ok)
|
|
8556
|
+
return true;
|
|
8557
|
+
if (alreadyExistsStatus && res2.status === alreadyExistsStatus)
|
|
8558
|
+
return true;
|
|
8559
|
+
return false;
|
|
8560
|
+
};
|
|
8561
|
+
if (existingOtp) {
|
|
8562
|
+
const res2 = await post(existingOtp);
|
|
8563
|
+
if (check(res2))
|
|
8564
|
+
return { otp: existingOtp, alreadyExists: !res2.ok };
|
|
8565
|
+
}
|
|
8566
|
+
let res = await post();
|
|
8567
|
+
if (check(res))
|
|
8568
|
+
return { otp: undefined, alreadyExists: !res.ok };
|
|
8569
|
+
if (res.status === 401) {
|
|
8570
|
+
const notice = res.headers.get("npm-notice") ?? "";
|
|
8571
|
+
const loginUrl = notice.match(/https:\/\/www\.npmjs\.com\/login\/[a-f0-9-]+/)?.[0];
|
|
8572
|
+
if (!loginUrl)
|
|
8573
|
+
throw new Error("2FA required but could not parse login URL");
|
|
8574
|
+
await $4`open ${loginUrl}`.quiet().nothrow();
|
|
8575
|
+
const otp = await promptPasscode();
|
|
8576
|
+
res = await post(otp);
|
|
8577
|
+
if (check(res))
|
|
8578
|
+
return { otp, alreadyExists: !res.ok };
|
|
8579
|
+
}
|
|
8580
|
+
throw new Error(`${res.status} ${await res.text()}`);
|
|
8581
|
+
}
|
|
8582
|
+
async function addTrustedPublisher(name, ownerRepo, token, environment = "prod") {
|
|
8583
|
+
const encoded = encodePackageName(name);
|
|
8584
|
+
log(" setting up (may require 2FA)...");
|
|
8585
|
+
const body = JSON.stringify([{
|
|
8586
|
+
type: "github",
|
|
8587
|
+
claims: { repository: ownerRepo, workflow_ref: { file: "publish.yml" }, environment },
|
|
8588
|
+
permissions: ["createPackage"]
|
|
8589
|
+
}]);
|
|
8590
|
+
try {
|
|
8591
|
+
const { otp, alreadyExists } = await registryPost(`https://registry.npmjs.org/-/package/${encoded}/trust`, body, token, { alreadyExistsStatus: 409 });
|
|
8592
|
+
return { alreadyConfigured: alreadyExists, otp };
|
|
8593
|
+
} catch (e) {
|
|
8594
|
+
throw new Error(`trust setup failed: ${e.message}`);
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
async function setPackageAccessPolicy(name, token, existingOtp) {
|
|
8598
|
+
const encoded = encodePackageName(name);
|
|
8599
|
+
const body = JSON.stringify({ publish_requires_tfa: true, automation_token_overrides_tfa: false });
|
|
8600
|
+
try {
|
|
8601
|
+
await registryPost(`https://registry.npmjs.org/-/package/${encoded}/access`, body, token, { existingOtp });
|
|
8602
|
+
} catch (e) {
|
|
8603
|
+
throw new Error(`access policy setup failed: ${e.message}`);
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
|
|
8607
|
+
// src/commands/npm/setup/git-remote.ts
|
|
8608
|
+
var {$: $5 } = globalThis.Bun;
|
|
8609
|
+
function parseGithubOwnerRepo(url) {
|
|
8610
|
+
const https = url.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
8611
|
+
if (https)
|
|
8612
|
+
return { owner: https[1], repo: https[2] };
|
|
8613
|
+
const alias = url.match(/^[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
8614
|
+
if (alias)
|
|
8615
|
+
return { owner: alias[1], repo: alias[2] };
|
|
8616
|
+
return null;
|
|
8617
|
+
}
|
|
8618
|
+
async function checkGitHubAccess(remoteUrl) {
|
|
8619
|
+
const parsed = parseGithubOwnerRepo(remoteUrl);
|
|
8620
|
+
if (!parsed)
|
|
8621
|
+
return null;
|
|
8622
|
+
const result = await $5`gh api repos/${parsed.owner}/${parsed.repo}`.quiet().nothrow();
|
|
8623
|
+
if (result.exitCode !== 0)
|
|
8624
|
+
return null;
|
|
8625
|
+
let data;
|
|
8626
|
+
try {
|
|
8627
|
+
data = JSON.parse(result.stdout.toString());
|
|
8628
|
+
} catch {
|
|
8629
|
+
return null;
|
|
8630
|
+
}
|
|
8631
|
+
return { ...parsed, canPush: data?.permissions?.push === true };
|
|
8632
|
+
}
|
|
8633
|
+
|
|
8634
|
+
// src/commands/npm/setup/workflow.ts
|
|
8635
|
+
import * as fs3 from "fs/promises";
|
|
8636
|
+
import * as path from "path";
|
|
8637
|
+
|
|
8638
|
+
// src/commands/npm/setup/publish-workflow.yml
|
|
8639
|
+
var publish_workflow_default = `name: Publish to npm
|
|
8640
|
+
|
|
8641
|
+
on:
|
|
8642
|
+
push:
|
|
8643
|
+
branches:
|
|
8644
|
+
- main
|
|
8645
|
+
workflow_dispatch: {}
|
|
8646
|
+
|
|
8647
|
+
jobs:
|
|
8648
|
+
publish:
|
|
8649
|
+
runs-on: ubuntu-latest
|
|
8650
|
+
environment: prod
|
|
8651
|
+
permissions:
|
|
8652
|
+
contents: read
|
|
8653
|
+
id-token: write
|
|
8654
|
+
steps:
|
|
8655
|
+
- name: Checkout
|
|
8656
|
+
uses: actions/checkout@v4
|
|
8657
|
+
|
|
8658
|
+
- uses: oven-sh/setup-bun@v2
|
|
8659
|
+
|
|
8660
|
+
- name: Setup Node
|
|
8661
|
+
uses: actions/setup-node@v4
|
|
8662
|
+
with:
|
|
8663
|
+
node-version: "24"
|
|
8664
|
+
registry-url: 'https://registry.npmjs.org'
|
|
8665
|
+
|
|
8666
|
+
- name: Install dependencies
|
|
8667
|
+
run: bun install
|
|
8668
|
+
|
|
8669
|
+
- name: Build
|
|
8670
|
+
run: bun run build
|
|
8671
|
+
|
|
8672
|
+
- name: Publish
|
|
8673
|
+
run: npm publish dist/ --access public
|
|
8674
|
+
`;
|
|
8675
|
+
|
|
8676
|
+
// src/commands/npm/setup/workflow.ts
|
|
8677
|
+
var WORKFLOW_PATH = ".github/workflows/publish.yml";
|
|
8678
|
+
async function workflowExists(repoRoot) {
|
|
8679
|
+
try {
|
|
8680
|
+
await fs3.access(path.join(repoRoot, WORKFLOW_PATH));
|
|
8681
|
+
return true;
|
|
8682
|
+
} catch {
|
|
8683
|
+
return false;
|
|
8684
|
+
}
|
|
8685
|
+
}
|
|
8686
|
+
async function createWorkflow(repoRoot) {
|
|
8687
|
+
const fullPath = path.join(repoRoot, WORKFLOW_PATH);
|
|
8688
|
+
await fs3.mkdir(path.dirname(fullPath), { recursive: true });
|
|
8689
|
+
await fs3.writeFile(fullPath, publish_workflow_default, "utf8");
|
|
8690
|
+
}
|
|
8691
|
+
|
|
8692
|
+
// src/commands/npm/setup/package-json-validate.ts
|
|
8693
|
+
var GITHUB_URL_RE = /^git\+https:\/\/github\.com\/[^/]+\/[^/]+\.git$/;
|
|
8694
|
+
function isValidGitHubUrl(url) {
|
|
8695
|
+
return GITHUB_URL_RE.test(url);
|
|
8696
|
+
}
|
|
8697
|
+
function validatePackageJson(pkg) {
|
|
8698
|
+
const issues = [];
|
|
8699
|
+
const repoUrl = typeof pkg.repository === "object" ? pkg.repository?.url : pkg.repository;
|
|
8700
|
+
if (!repoUrl) {
|
|
8701
|
+
issues.push({ field: "repository", issue: "missing \u2014 required for provenance" });
|
|
8702
|
+
} else if (!isValidGitHubUrl(repoUrl)) {
|
|
8703
|
+
issues.push({ field: "repository.url", issue: `not a valid GitHub URL (got "${repoUrl}")` });
|
|
8704
|
+
}
|
|
8705
|
+
if (pkg.publishConfig?.access !== "public") {
|
|
8706
|
+
issues.push({ field: "publishConfig.access", issue: 'should be "public"' });
|
|
8707
|
+
}
|
|
8708
|
+
if (pkg.publishConfig?.provenance !== true) {
|
|
8709
|
+
issues.push({ field: "publishConfig.provenance", issue: "should be true" });
|
|
8710
|
+
}
|
|
8711
|
+
if (!pkg.version) {
|
|
8712
|
+
issues.push({ field: "version", issue: 'missing \u2014 add a "version" field (e.g. "0.0.1")', fatal: true });
|
|
8713
|
+
}
|
|
8714
|
+
if (!pkg.scripts?.build) {
|
|
8715
|
+
issues.push({ field: "scripts.build", issue: 'missing \u2014 add a "build" script before running setup', fatal: true });
|
|
8716
|
+
}
|
|
8717
|
+
if (!pkg.files?.includes("dist")) {
|
|
8718
|
+
issues.push({ field: "files", issue: `"dist" not included \u2014 built output won't be published` });
|
|
8719
|
+
}
|
|
8720
|
+
return issues;
|
|
8721
|
+
}
|
|
8722
|
+
|
|
8723
|
+
// src/commands/npm/setup/index.ts
|
|
8724
|
+
var setup2 = makeCommand("setup", "configure a new npm package for publishing", async () => {
|
|
8725
|
+
const cwd = process.cwd();
|
|
8726
|
+
log("checking npm...");
|
|
8727
|
+
const npmCheck = await $6`which npm`.quiet().nothrow();
|
|
8728
|
+
if (npmCheck.exitCode !== 0) {
|
|
8729
|
+
return Err("NpmNotFound" /* NpmNotFound */, '"npm" not found in PATH \u2014 install Node.js from nodejs.org');
|
|
8730
|
+
}
|
|
8731
|
+
const npmVersion = (await $6`npm --version`.quiet().nothrow()).stdout.toString().trim();
|
|
8732
|
+
const [major, minor] = npmVersion.split(".").map(Number);
|
|
8733
|
+
if (major < 11 || major === 11 && minor < 10) {
|
|
8734
|
+
return Err("NpmNotFound" /* NpmNotFound */, `npm ${npmVersion} is too old \u2014 "npm trust" requires 11.10.0+ (run "npm install -g npm")`);
|
|
8735
|
+
}
|
|
8736
|
+
let npmToken, npmUser;
|
|
8737
|
+
try {
|
|
8738
|
+
({ token: npmToken, user: npmUser } = await acquireToken());
|
|
8739
|
+
} catch (e) {
|
|
8740
|
+
return Err("NotLoggedIn" /* NotLoggedIn */, `npm authentication failed: ${e.message}`);
|
|
8741
|
+
}
|
|
8742
|
+
log(` \u2713 logged in as ${npmUser}`);
|
|
8743
|
+
log("checking GitHub...");
|
|
8744
|
+
const ghCheck = await $6`which gh`.quiet().nothrow();
|
|
8745
|
+
if (ghCheck.exitCode !== 0) {
|
|
8746
|
+
return Err("GhNotFound" /* GhNotFound */, '"gh" not found in PATH \u2014 install GitHub CLI from cli.github.com');
|
|
8747
|
+
}
|
|
8748
|
+
let ghUser = await $6`gh api user --jq .login`.quiet().nothrow();
|
|
8749
|
+
if (ghUser.exitCode !== 0) {
|
|
8750
|
+
log(" not logged in \u2014 opening gh auth login...");
|
|
8751
|
+
await $6`gh auth login`.nothrow();
|
|
8752
|
+
ghUser = await $6`gh api user --jq .login`.quiet().nothrow();
|
|
8753
|
+
}
|
|
8754
|
+
if (ghUser.exitCode !== 0) {
|
|
8755
|
+
return Err("NotLoggedIn" /* NotLoggedIn */, 'not logged into GitHub \u2014 run "gh auth login"');
|
|
8756
|
+
}
|
|
8757
|
+
log(` \u2713 logged in as ${ghUser.stdout.toString().trim()}`);
|
|
8758
|
+
log("checking git repo...");
|
|
8759
|
+
if (!existsSync4(`${cwd}/.git`)) {
|
|
8760
|
+
return Err("NoGitRepo" /* NoGitRepo */, 'current directory is not a git repository \u2014 run "git init" first');
|
|
8761
|
+
}
|
|
8762
|
+
log(" \u2713 git repo");
|
|
8763
|
+
log("checking package.json...");
|
|
8764
|
+
const pkgFile = Bun.file(`${cwd}/package.json`);
|
|
8765
|
+
if (!await pkgFile.exists()) {
|
|
8766
|
+
return Err("NoPackageJson" /* NoPackageJson */, "no package.json found \u2014 run this command from a package directory");
|
|
8767
|
+
}
|
|
8768
|
+
let pkg;
|
|
8769
|
+
try {
|
|
8770
|
+
pkg = await pkgFile.json();
|
|
8771
|
+
} catch {
|
|
8772
|
+
return Err("InvalidPackageJson" /* InvalidPackageJson */, "package.json contains invalid JSON \u2014 fix it and retry");
|
|
8773
|
+
}
|
|
8774
|
+
const { name } = pkg;
|
|
8775
|
+
if (!name) {
|
|
8776
|
+
return Err("InvalidPackageJson" /* InvalidPackageJson */, 'package.json is missing a "name" field');
|
|
8777
|
+
}
|
|
8778
|
+
log(` name: ${name}`);
|
|
8779
|
+
const fatal = validatePackageJson(pkg).find((i) => i.fatal);
|
|
8780
|
+
if (fatal) {
|
|
8781
|
+
return Err("InvalidPackageJson" /* InvalidPackageJson */, `${fatal.field}: ${fatal.issue}`);
|
|
8782
|
+
}
|
|
8783
|
+
log("checking git remote...");
|
|
8784
|
+
const remote = await getRemoteUrl(cwd);
|
|
8785
|
+
if (!remote) {
|
|
8786
|
+
return Err("NoGitRemote" /* NoGitRemote */, 'no git remote configured \u2014 run "git remote add origin <url>" first');
|
|
8787
|
+
}
|
|
8788
|
+
log(` remote: ${remote}`);
|
|
8789
|
+
const access2 = await checkGitHubAccess(remote);
|
|
8790
|
+
if (!access2) {
|
|
8791
|
+
return Err("RepoNotFound" /* RepoNotFound */, `could not reach GitHub repo at ${remote} \u2014 check "gh auth status"`);
|
|
8792
|
+
}
|
|
8793
|
+
if (!access2.canPush) {
|
|
8794
|
+
return Err("NoWriteAccess" /* NoWriteAccess */, `no push access to ${access2.owner}/${access2.repo} \u2014 you need write permissions`);
|
|
8795
|
+
}
|
|
8796
|
+
const repoHttpsUrl = `https://github.com/${access2.owner}/${access2.repo}`;
|
|
8797
|
+
log(` \u2713 ${access2.owner}/${access2.repo} (write access confirmed)`);
|
|
8798
|
+
const expectedRepoUrl = `git+${repoHttpsUrl}.git`;
|
|
8799
|
+
const currentRepoUrl = typeof pkg.repository === "object" ? pkg.repository?.url : pkg.repository;
|
|
8800
|
+
if (currentRepoUrl !== expectedRepoUrl) {
|
|
8801
|
+
log("syncing package.json repository URL...");
|
|
8802
|
+
log(` was: ${currentRepoUrl ?? "(missing)"}`);
|
|
8803
|
+
pkg = { ...pkg, repository: { type: "git", url: expectedRepoUrl } };
|
|
8804
|
+
await Bun.write(`${cwd}/package.json`, JSON.stringify(pkg, null, 2) + `
|
|
8805
|
+
`);
|
|
8806
|
+
log(` \u2713 set to ${expectedRepoUrl}`);
|
|
8807
|
+
}
|
|
8808
|
+
const nonFatal = validatePackageJson(pkg).filter((i) => !i.fatal);
|
|
8809
|
+
if (nonFatal.length > 0) {
|
|
8810
|
+
log("package.json issues (fix manually):");
|
|
8811
|
+
for (const { field, issue } of nonFatal)
|
|
8812
|
+
log(` ${field}: ${issue}`);
|
|
8813
|
+
}
|
|
8814
|
+
log("checking publish workflow...");
|
|
8815
|
+
if (!await workflowExists(cwd)) {
|
|
8816
|
+
await createWorkflow(cwd);
|
|
8817
|
+
log(" \u2713 created .github/workflows/publish.yml");
|
|
8818
|
+
} else {
|
|
8819
|
+
log(" \u2713 already exists");
|
|
8820
|
+
}
|
|
8821
|
+
log("checking npm registry...");
|
|
8822
|
+
const info = await fetchPackageInfo(name).catch((err) => {
|
|
8823
|
+
throw new Error(`npm registry lookup failed: ${err.message}`);
|
|
8824
|
+
});
|
|
8825
|
+
if (!info) {
|
|
8826
|
+
log(" package not yet published \u2014 building and creating initial release...");
|
|
8827
|
+
const buildResult = await $6`bun run build`.nothrow();
|
|
8828
|
+
if (buildResult.exitCode !== 0) {
|
|
8829
|
+
throw new Error(`build failed: ${buildResult.stderr.toString().trim()}`);
|
|
8830
|
+
}
|
|
8831
|
+
const distPkgFile = Bun.file(`${cwd}/dist/package.json`);
|
|
8832
|
+
if (!await distPkgFile.exists()) {
|
|
8833
|
+
throw new Error("build succeeded but dist/package.json is missing \u2014 build script must produce it");
|
|
8834
|
+
}
|
|
8835
|
+
try {
|
|
8836
|
+
await distPkgFile.json();
|
|
8837
|
+
} catch {
|
|
8838
|
+
throw new Error("dist/package.json exists but contains invalid JSON \u2014 check your build script");
|
|
8839
|
+
}
|
|
8840
|
+
await npmPublish(npmToken);
|
|
8841
|
+
log(` \u2713 published ${name}`);
|
|
8842
|
+
} else {
|
|
8843
|
+
const version2 = info.latestVersion ?? "(unknown)";
|
|
8844
|
+
log(` \u2713 exists (latest v${version2})`);
|
|
8845
|
+
}
|
|
8846
|
+
log("checking trusted publishing...");
|
|
8847
|
+
const ownerRepo = `${access2.owner}/${access2.repo}`;
|
|
8848
|
+
const trust = await addTrustedPublisher(name, ownerRepo, npmToken);
|
|
8849
|
+
log(trust.alreadyConfigured ? " \u2713 already configured" : " \u2713 configured");
|
|
8850
|
+
log("setting package access policy...");
|
|
8851
|
+
await setPackageAccessPolicy(name, npmToken, trust.otp);
|
|
8852
|
+
log(" \u2713 configured");
|
|
8853
|
+
return Ok(true);
|
|
8854
|
+
});
|
|
8855
|
+
async function getRemoteUrl(cwd) {
|
|
8856
|
+
const result = await $6`git -C ${cwd} remote get-url origin`.quiet().nothrow();
|
|
8857
|
+
if (result.exitCode !== 0)
|
|
8858
|
+
return;
|
|
8859
|
+
return result.stdout.toString().trim();
|
|
8860
|
+
}
|
|
8861
|
+
async function npmPublish(token) {
|
|
8862
|
+
const proc = Bun.spawn(["npm", "publish", "dist/", "--access", "public", "--no-provenance"], {
|
|
8863
|
+
stdin: "inherit",
|
|
8864
|
+
stdout: "inherit",
|
|
8865
|
+
stderr: "inherit",
|
|
8866
|
+
env: { ...process.env, NODE_AUTH_TOKEN: token }
|
|
8867
|
+
});
|
|
8868
|
+
const exitCode = await proc.exited;
|
|
8869
|
+
if (exitCode !== 0)
|
|
8870
|
+
throw new Error("npm publish failed \u2014 see output above");
|
|
8871
|
+
}
|
|
8872
|
+
|
|
8873
|
+
// src/commands/npm/index.ts
|
|
8874
|
+
var npm = new Command("npm").summary("npm package management utilities");
|
|
8875
|
+
[setup2].forEach((cmd) => {
|
|
8876
|
+
npm.addCommand(cmd);
|
|
8877
|
+
});
|
|
8878
|
+
|
|
8449
8879
|
// src/commands/index.ts
|
|
8450
8880
|
program.name("ozy").version(version);
|
|
8451
|
-
[git].forEach((cmd) => {
|
|
8881
|
+
[git, npm].forEach((cmd) => {
|
|
8452
8882
|
program.addCommand(cmd);
|
|
8453
8883
|
});
|
|
8454
8884
|
program.parse();
|