@moku-labs/ci 1.2.4 → 1.2.5

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 CHANGED
@@ -22,6 +22,7 @@ everything else lives here, once. Not a build tool and not a framework — it ca
22
22
  [Install](#install) ·
23
23
  [How it works](#how-it-works) ·
24
24
  [Workflows](#workflows) ·
25
+ [Project checks](#project-checks) ·
25
26
  [PR previews](#pr-previews) ·
26
27
  [CLI](#cli) ·
27
28
  [The contract](#the-contract) ·
@@ -94,8 +95,8 @@ flowchart LR
94
95
 
95
96
  | Workflow | Called from | Jobs | Inputs (all optional) |
96
97
  |---|---|---|---|
97
- | [`package-ci.yml`](.github/workflows/package-ci.yml) | [`examples/package/ci.yml`](examples/package/ci.yml) | `lint` · `types` · `test` · `build` · `preview` | `runs_on`, `bun_version`, `validate`, `preview` |
98
- | [`package-release.yml`](.github/workflows/package-release.yml) | [`examples/package/publish.yml`](examples/package/publish.yml) | `check` → `release` → `package` → `publish` | `release_type`, `publish`, `runs_on`, `bun_version`, `node_version`, `artifact_name`, `validate` |
98
+ | [`package-ci.yml`](.github/workflows/package-ci.yml) | [`examples/package/ci.yml`](examples/package/ci.yml) | `lint` · `types` · `test` · `build` · `extra` · `preview` | `runs_on`, `bun_version`, `validate`, `preview`, `extra` |
99
+ | [`package-release.yml`](.github/workflows/package-release.yml) | [`examples/package/publish.yml`](examples/package/publish.yml) | `check` → `release` → `package` → `publish` | `release_type`, `publish`, `runs_on`, `bun_version`, `node_version`, `artifact_name`, `validate`, `extra` |
99
100
  | [`app-deploy.yml`](.github/workflows/app-deploy.yml) | [`examples/app/ci.yml`](examples/app/ci.yml) | `validate` → `deploy` to Cloudflare | script names (`lint_script`, `build_script`, `deploy_script`, …) and two required secrets |
100
101
  | [`self-test.yml`](.github/workflows/self-test.yml) | this repo only | `actionlint` over workflows and examples | — |
101
102
 
@@ -110,6 +111,30 @@ flowchart LR
110
111
  > A Layer-3 app copies `examples/app/ci.yml` by hand and needs a `deploy` script. The CLI
111
112
  > sets up packages only.
112
113
 
114
+ ## Project checks
115
+
116
+ A check the five contract scripts do not cover goes in `extra`: a JSON array of script names.
117
+ Each one is its own job, run after a build, and reported as `ci / extra (check:bundle)`.
118
+
119
+ ```yaml
120
+ # .github/workflows/ci.yml
121
+ jobs:
122
+ ci:
123
+ uses: moku-labs/ci/.github/workflows/package-ci.yml@v1
124
+ with:
125
+ extra: '["check:bundle", "test:cli-e2e"]'
126
+ ```
127
+
128
+ | Rule | Where |
129
+ |---|---|
130
+ | Give `publish.yml` the same `extra`, so a release waits for the same checks. | caller `publish.yml` |
131
+ | `setup` never replaces a thin caller that differs from the template. | `moku-release setup` |
132
+ | An `extra` job is not a required check until the ruleset of that repo names it. | repo ruleset |
133
+ | A check that needs its own matrix or a cron stays a separate workflow file. `doctor` and `setup` only look at `ci.yml` and `publish.yml`. | the project |
134
+
135
+ A package may keep its version in git tags only. `build` and `preview` then seed
136
+ `package.json` from the latest `v*` tag before they pack. Nothing is committed.
137
+
113
138
  ## PR previews
114
139
 
115
140
  Every pull request commit is published to [pkg.pr.new](https://pkg.pr.new) by the `preview`
package/dist/release.mjs CHANGED
@@ -2217,16 +2217,15 @@ async function isCommittedUnchanged(setup, path) {
2217
2217
  *
2218
2218
  * @param setup - The wizard state.
2219
2219
  * @param template - The central workflow about to be written.
2220
- * @param existing - The current contents of the file.
2221
2220
  * @returns `true` when the caller may write the template now.
2222
2221
  * @example
2223
- * if (!(await clearExistingWorkflow(setup, template, existing))) continue;
2222
+ * if (!(await clearExistingWorkflow(setup, template))) continue;
2224
2223
  */
2225
- async function clearExistingWorkflow(setup, template, existing) {
2226
- const kind = isThinWorkflow(existing, template) ? "differs" : "is a legacy workflow";
2224
+ async function clearExistingWorkflow(setup, template) {
2227
2225
  const committed = await isCommittedUnchanged(setup, template.path);
2228
2226
  const safety = committed ? "git keeps the original" : "a .bak copy is kept";
2229
- if (!await setup.prompts.confirm(`${template.path} ${kind}. Replace it (${safety})?`)) {
2227
+ const question = `${template.path} is a legacy workflow. Replace it (${safety})?`;
2228
+ if (!await setup.prompts.confirm(question)) {
2230
2229
  setup.ui.check(false, `${template.path} left unchanged`);
2231
2230
  return false;
2232
2231
  }
@@ -2252,7 +2251,11 @@ async function writeWorkflows(setup) {
2252
2251
  setup.ui.check(true, `${template.path} up to date`);
2253
2252
  continue;
2254
2253
  }
2255
- if (!(existing === void 0 ? !deferred(setup, `write ${template.path}`) : await clearExistingWorkflow(setup, template, existing))) continue;
2254
+ if (existing !== void 0 && isThinWorkflow(existing, template)) {
2255
+ setup.ui.check(true, `${template.path} is a thin caller with its own inputs, left as is`);
2256
+ continue;
2257
+ }
2258
+ if (!(existing === void 0 ? !deferred(setup, `write ${template.path}`) : await clearExistingWorkflow(setup, template))) continue;
2256
2259
  await setup.ctx.files.write(template.path, template.content);
2257
2260
  setup.ui.check(true, `${template.path} written`);
2258
2261
  }
@@ -2295,10 +2298,10 @@ async function normalizeContract(setup, manifest) {
2295
2298
  *
2296
2299
  * @param setup - The wizard state.
2297
2300
  * @param name - The package name.
2298
- * @param version - The version about to be published.
2299
- * @returns `true` when this run performed the first publish.
2301
+ * @param version - The version about to be published; a released package may keep none.
2302
+ * @returns Where the package stands on npm after this step.
2300
2303
  * @example
2301
- * await firstPublish(setup, "@moku-labs/common", "0.2.0");
2304
+ * const state = await firstPublish(setup, "@moku-labs/common", "0.2.0");
2302
2305
  */
2303
2306
  async function firstPublish(setup, name, version) {
2304
2307
  setup.ui.heading("First publish");
@@ -2309,17 +2312,21 @@ async function firstPublish(setup, name, version) {
2309
2312
  ]);
2310
2313
  if (view.code === 0) {
2311
2314
  setup.ui.check(true, `${name}@${view.stdout.trim()} already on npm`);
2312
- return false;
2315
+ return "present";
2316
+ }
2317
+ if (version === void 0) {
2318
+ setup.ui.check(false, `${MANIFEST_PATH} has no \`version\`, a first publish needs one`);
2319
+ return "absent";
2313
2320
  }
2314
2321
  if (!await setup.prompts.confirm(`Publish ${name}@${version} to npm now?`)) {
2315
2322
  setup.ui.check(false, "first publish skipped");
2316
- return false;
2323
+ return "absent";
2317
2324
  }
2318
- if (deferred(setup, `run bun run build && npm publish --access public`)) return false;
2319
- if (needsOwner(setup, "first publish", "bun run build && npm publish --access public")) return false;
2325
+ if (deferred(setup, `run bun run build && npm publish --access public`)) return "published";
2326
+ if (needsOwner(setup, "first publish", "bun run build && npm publish --access public")) return "absent";
2320
2327
  if (await setup.ctx.exec.inherit("bun", ["run", "build"]) !== 0) {
2321
2328
  setup.ui.error("build failed — not publishing");
2322
- return false;
2329
+ return "absent";
2323
2330
  }
2324
2331
  const published = await setup.ctx.exec.inherit("npm", [
2325
2332
  "publish",
@@ -2327,7 +2334,7 @@ async function firstPublish(setup, name, version) {
2327
2334
  "public"
2328
2335
  ]);
2329
2336
  setup.ui.check(published === 0, `npm publish ${name}@${version}`);
2330
- return published === 0;
2337
+ return published === 0 ? "published" : "absent";
2331
2338
  }
2332
2339
  /**
2333
2340
  * Tag the published version and push ONLY that tag — the branch is never written from
@@ -2346,6 +2353,10 @@ async function pushVersionTag(setup, version) {
2346
2353
  setup.ui.check(true, `release tags exist, latest is ${latest}`);
2347
2354
  return;
2348
2355
  }
2356
+ if (version === void 0) {
2357
+ setup.ui.check(false, `${MANIFEST_PATH} has no \`version\` and the repo has no release tag`);
2358
+ return;
2359
+ }
2349
2360
  const tag = `v${version}`;
2350
2361
  if ((await setup.ctx.exec.capture("git", [
2351
2362
  "tag",
@@ -2492,14 +2503,17 @@ async function runSetup(options) {
2492
2503
  });
2493
2504
  if (!await ensurePrerequisites(setup)) return 1;
2494
2505
  const manifest = await readManifest(setup.ctx.files);
2495
- if (!manifest?.name || !manifest.version) {
2496
- ui.error(`${MANIFEST_PATH} is missing, malformed, or has no name/version`);
2506
+ if (!manifest?.name) {
2507
+ ui.error(`${MANIFEST_PATH} is missing, malformed, or has no name`);
2497
2508
  return 1;
2498
2509
  }
2499
2510
  await writeWorkflows(setup);
2500
2511
  await normalizeContract(setup, manifest);
2501
- const justPublished = await firstPublish(setup, manifest.name, manifest.version);
2502
- await pushVersionTag(setup, manifest.version);
2512
+ const published = await firstPublish(setup, manifest.name, manifest.version);
2513
+ if (published === "absent") {
2514
+ ui.heading("Tag");
2515
+ ui.check(false, "no version tag pushed, the package is not on npm yet");
2516
+ } else await pushVersionTag(setup, manifest.version);
2503
2517
  await registerTrustedPublisher(setup);
2504
2518
  const declared = repositoryUrlOf(manifest);
2505
2519
  const ownerRepo = declared === void 0 ? void 0 : ownerRepoFrom(declared);
@@ -2510,7 +2524,7 @@ async function runSetup(options) {
2510
2524
  ctx: setup.ctx,
2511
2525
  ui
2512
2526
  });
2513
- if (justPublished) ui.info("published just now: npm can answer 404 for a few minutes, re-run release:doctor then");
2527
+ if (published === "published" && !setup.dryRun) ui.info("published just now: npm can answer 404 for a few minutes, re-run release:doctor then");
2514
2528
  return report.failed ? 1 : 0;
2515
2529
  }
2516
2530
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/ci",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Central CI and release for the moku family: reusable workflows, caller examples and the moku-release CLI.",
5
5
  "type": "module",
6
6
  "sideEffects": [