Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/release-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion release/compatibility.json
Original file line number Diff line number Diff line change
@@ -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"]
Expand Down
79 changes: 77 additions & 2 deletions scripts/smoke-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
lstatSync,
mkdtempSync,
mkdirSync,
realpathSync,
readFileSync,
rmSync,
writeFileSync,
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -692,7 +767,7 @@ function sha256(value) {
}

/**
* @param {ReturnType<typeof spawnPackedCli>} execution
* @param {{status: number | null, stdout: string, stderr: string}} execution
* @param {number} status
* @param {Record<string, unknown>} error
*/
Expand Down
14 changes: 14 additions & 0 deletions test/package.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/,
Expand Down