diff --git a/docs/release-history.md b/docs/release-history.md index fce0477..1910077 100644 --- a/docs/release-history.md +++ b/docs/release-history.md @@ -38,3 +38,11 @@ source or documentation does not mutate either dist-tag. promoted stable release. - Package version `0.2.0` and protected tag `v0.2.0` are consumed and immutable. A backward-compatible addition to the `0.2.x` line therefore requires a higher patch version rather than reusing those identities. + +## 0.2.1 candidate publication + +- On August 28, 2026, protected tag `v0.2.1` published ordinary version `0.2.1` under `next`; `latest` remained + `0.1.0`. The patch hardened direct Compilation recovery while preserving the `0.2.x` compatibility line. +- Package version `0.2.1` and protected tag `v0.2.1` are consumed and immutable. As observed on August 29, 2026, + current-directory root-output had integrated to `main` at `4352f64baf673ad93457e8bc84273e9d1d9a9501` (tree + `b43ba6de98e27328e548cc3410ba9f39dfa9fcee`) but was not part of those registry bytes. diff --git a/package-lock.json b/package-lock.json index 6ba64af..addb262 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@firstdraft.com/cli", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@firstdraft.com/cli", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "bin": { "firstdraft": "bin/firstdraft.js" diff --git a/package.json b/package.json index 1dab0ec..4ced463 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@firstdraft.com/cli", - "version": "0.2.1", + "version": "0.2.2", "description": "Command-line interface for First Draft", "license": "MIT", "type": "module", diff --git a/release/compatibility.json b/release/compatibility.json index b979685..2e4e193 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -1,7 +1,7 @@ { "format": "firstdraft.release-compatibility/1", "component": "cli", - "version": "0.2.1", + "version": "0.2.2", "requires": { "api_contract": [">= 0.3.0", "< 0.4.0"], "foundation_plan_formats": ["firstdraft.foundation-plan.sketch/0.19"] diff --git a/scripts/smoke-package.js b/scripts/smoke-package.js index ad364de..c106266 100644 --- a/scripts/smoke-package.js +++ b/scripts/smoke-package.js @@ -6,6 +6,7 @@ import { lstatSync, mkdtempSync, mkdirSync, + realpathSync, readFileSync, rmSync, writeFileSync, @@ -652,11 +653,85 @@ First Draft: Application compiled. 0o644, ); } + + const rootOutput = await spawnPackedCliAsync( + ["plan", "compile", "--output", "."], + projectDirectory, + ); + const expectedCompilationStarts = process.platform === "win32" ? 1 : 2; + + if (process.platform === "win32") { + assertHandledFailure(rootOutput, 2, { + error: "invalid_output_path", + detail: + "The compilation output must be an absent path beneath an existing real directory or the eligible current directory.", + reason: "root_platform_unsupported", + }); + } else { + const realProjectDirectory = realpathSync(projectDirectory); + assert.equal(rootOutput.status, 0); + assert.equal( + rootOutput.stderr, + `First Draft: Analyzing Foundation Plan... +First Draft: Foundation Plan analysis valid. +First Draft: Compiling application... +First Draft: Application compiled. +`, + ); + assert.deepEqual(JSON.parse(rootOutput.stdout).output, { + path: realProjectDirectory, + file_count: 1, + manifest_sha256: manifestSha256, + root_adoption: { + design_path: path.join(realProjectDirectory, "design"), + moved_entry_count: 3, + git_repository_preserved: false, + git_index_replaced: false, + }, + }); + assert.equal( + readFileSync( + path.join(realProjectDirectory, "app/models/movie.rb"), + "utf8", + ), + contents.toString("utf8"), + ); + assert.deepEqual( + readFileSync( + path.join( + realProjectDirectory, + "design/.firstdraft/foundation-plan.json", + ), + ), + plan, + ); + assert.equal( + readFileSync( + path.join( + realProjectDirectory, + "design/application/app/models/movie.rb", + ), + "utf8", + ), + contents.toString("utf8"), + ); + assert.equal( + readFileSync( + path.join( + realProjectDirectory, + "design/generated/app/models/movie.rb", + ), + "utf8", + ), + contents.toString("utf8"), + ); + } + assert.deepEqual(seen, { plan: true, analysis: true, publication: 1, - compilationStarts: 1, + compilationStarts: expectedCompilationStarts, status: true, artifact: true, }); @@ -692,7 +767,7 @@ function sha256(value) { } /** - * @param {ReturnType} execution + * @param {{status: number | null, stdout: string, stderr: string}} execution * @param {number} status * @param {Record} error */ diff --git a/test/package.test.js b/test/package.test.js index 39c6ecd..44ae034 100644 --- a/test/package.test.js +++ b/test/package.test.js @@ -130,6 +130,20 @@ test("stable release completion requires qualified latest promotion", () => { releaseHistory, /Protected tag `v0\.1\.0` and package version `0\.1\.0` were consumed and immutable/, ); + assert.match( + releaseHistory, + /Package version `0\.2\.1` and protected tag `v0\.2\.1` are consumed and immutable[\s\S]*?As observed on August 29, 2026,[\s\S]*?current-directory root-output[\s\S]*?`4352f64baf673ad93457e8bc84273e9d1d9a9501`[\s\S]*?`b43ba6de98e27328e548cc3410ba9f39dfa9fcee`[\s\S]*?was not part of those registry bytes/, + ); + const consumedVersions = [ + ...releaseHistory.matchAll( + /(?:Package|package) version `([^`]+)`(?=[\s\S]{0,120}?consumed and immutable)/g, + ), + ].map((match) => match[1]); + assert.equal( + consumedVersions.includes(metadata.version), + false, + `package version ${metadata.version} is already recorded as consumed`, + ); assert.match( releasingGuide, /If either identity is already[\s\S]*?consumed, prepare the next version required by the pre-1\.0 policy rather than moving or reusing it/,