create-portfoliable 1.2.2 → 1.2.4

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
@@ -26,3 +26,5 @@ npx create-portfoliable my-site
26
26
  ## License
27
27
 
28
28
  MIT
29
+
30
+ This package is published through the Portfoliable npm org and the GitHub Packages release workflow.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-portfoliable",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Unified Portfoliable package with initializer and runtime CLI",
5
5
  "private": false,
6
6
  "type": "module",
@@ -5,6 +5,7 @@
5
5
  // MARK: IMPORTS
6
6
  import { execFileSync } from "node:child_process";
7
7
  import { readFileSync, writeFileSync, appendFileSync, existsSync } from "node:fs";
8
+ import { pathToFileURL } from "node:url";
8
9
 
9
10
  // MARK: RELEASE CONFIGURATION
10
11
  // Defines canonical repository URL used for changelog compare and commit links.
@@ -19,6 +20,7 @@ const packages = [
19
20
  {
20
21
  key: "package",
21
22
  displayName: "create-portfoliable",
23
+ npmPackageName: "create-portfoliable",
22
24
  packageJsonPath: "create-portfoliable/package.json",
23
25
  changelogPath: "CHANGELOG.md",
24
26
  tagPrefix: "v",
@@ -175,6 +177,46 @@ function bumpVersion(currentVersion, level) {
175
177
  return `${major}.${minor}.${patch + 1}`;
176
178
  }
177
179
 
180
+ export function parsePublishedVersionList(rawResponse) {
181
+ if (!rawResponse || rawResponse.trim() === "") {
182
+ return [];
183
+ }
184
+
185
+ try {
186
+ const parsed = JSON.parse(rawResponse);
187
+ if (!Array.isArray(parsed)) {
188
+ return [];
189
+ }
190
+
191
+ return parsed
192
+ .filter((value) => typeof value === "string")
193
+ .map((value) => value.trim())
194
+ .filter(Boolean);
195
+ } catch {
196
+ return [];
197
+ }
198
+ }
199
+
200
+ export function isVersionPublished(publishedVersions, candidateVersion) {
201
+ return Array.isArray(publishedVersions) && publishedVersions.includes(candidateVersion);
202
+ }
203
+
204
+ function getPublishedVersionsForPackage(packageName) {
205
+ try {
206
+ const output = execFileSync("npm", ["view", packageName, "versions", "--json"], {
207
+ encoding: "utf8",
208
+ stdio: ["ignore", "pipe", "pipe"],
209
+ }).trim();
210
+ return parsePublishedVersionList(output);
211
+ } catch (error) {
212
+ const message = error?.stderr ? String(error.stderr) : String(error);
213
+ if (message.includes("E404") || message.includes("404 Not Found")) {
214
+ return [];
215
+ }
216
+ throw error;
217
+ }
218
+ }
219
+
178
220
  // Updates package.json version field with computed release version.
179
221
  function updatePackageJson(filePath, nextVersion) {
180
222
  // Reads and parses package manifest.
@@ -267,6 +309,14 @@ function createReleasePlan(pkg) {
267
309
  const currentVersion = JSON.parse(readFileSync(pkg.packageJsonPath, "utf8")).version;
268
310
  // Computes next version using selected bump level.
269
311
  const nextVersion = bumpVersion(currentVersion, bump);
312
+ const publishedVersions = getPublishedVersionsForPackage(pkg.npmPackageName);
313
+
314
+ if (isVersionPublished(publishedVersions, nextVersion)) {
315
+ throw new Error(
316
+ `Refusing to publish ${pkg.displayName} version ${nextVersion}: it is already published to npm (${pkg.npmPackageName}). Please bump the version before releasing.`
317
+ );
318
+ }
319
+
270
320
  // Builds package tag string from prefix and next version.
271
321
  const nextTag = `${pkg.tagPrefix}${nextVersion}`;
272
322
 
@@ -359,5 +409,10 @@ function run() {
359
409
  }
360
410
  }
361
411
 
362
- // Executes orchestrator logic as the script main entrypoint.
363
- run();
412
+ const isMainModule =
413
+ typeof process.argv[1] === "string" &&
414
+ import.meta.url === pathToFileURL(process.argv[1]).href;
415
+
416
+ if (isMainModule) {
417
+ run();
418
+ }
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { isVersionPublished, parsePublishedVersionList } from './release-orchestrator.mjs';
3
+
4
+ describe('release orchestrator version guard', () => {
5
+ it('detects versions already published to npm', () => {
6
+ expect(parsePublishedVersionList('[]')).toEqual([]);
7
+ expect(parsePublishedVersionList('["1.2.2","1.2.3"]')).toEqual(['1.2.2', '1.2.3']);
8
+ expect(isVersionPublished(['1.2.2', '1.2.3'], '1.2.3')).toBe(true);
9
+ expect(isVersionPublished(['1.2.2', '1.2.3'], '1.2.4')).toBe(false);
10
+ });
11
+ });