Skip to content

Commit 679b8c0

Browse files
committed
fix(builder): recover localnet publish ids
Recover the extension package ID from the ephemeral pubfile when test-publish JSON does not include a published package change. Also seed a fresh Pub.extension.toml from the copied world pubfile so repeated localnet extension publishes remain idempotent.
1 parent 999705b commit 679b8c0

2 files changed

Lines changed: 112 additions & 19 deletions

File tree

pkg/builder/builder_test.go

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,63 @@ func TestExtractPublishIDs_CaseInsensitiveConfig(t *testing.T) {
153153
assert.Equal(t, "0xLOWER", cfgID)
154154
}
155155

156+
func TestGetLastPublishedAt(t *testing.T) {
157+
pubfile := filepath.Join(t.TempDir(), "Pub.localnet.toml")
158+
content := `# generated by Move
159+
build-env = "testnet"
160+
chain-id = "localnet"
161+
162+
[[published]]
163+
source = { local = "/workspace/world-contracts/contracts/world" }
164+
published-at = "0xWORLD"
165+
original-id = "0xWORLD"
166+
version = 0
167+
168+
[[published]]
169+
source = { local = "/workspace/builder-scaffold/move-contracts/smart_gate_extension" }
170+
published-at = "0xEXT"
171+
original-id = "0xEXT"
172+
version = 0
173+
`
174+
require.NoError(t, os.WriteFile(pubfile, []byte(content), 0600))
175+
176+
pkgID, err := getLastPublishedAt(pubfile)
177+
require.NoError(t, err)
178+
assert.Equal(t, "0xEXT", pkgID)
179+
}
180+
181+
func TestWritePublishedIDs_FallsBackToPubfile(t *testing.T) {
182+
workspace := t.TempDir()
183+
builderDir := filepath.Join(workspace, "builder-scaffold")
184+
require.NoError(t, os.MkdirAll(filepath.Join(builderDir, "deployments", "localnet"), 0750))
185+
require.NoError(t, os.WriteFile(filepath.Join(builderDir, ".env"), []byte("BUILDER_PACKAGE_ID=\nEXTENSION_CONFIG_ID=\n"), 0600))
186+
187+
pubfile := filepath.Join(builderDir, "deployments", "localnet", "Pub.localnet.toml")
188+
require.NoError(t, os.WriteFile(pubfile, []byte(`build-env = "testnet"
189+
chain-id = "localnet"
190+
191+
[[published]]
192+
source = { local = "/workspace/world-contracts/contracts/world" }
193+
published-at = "0xWORLD"
194+
original-id = "0xWORLD"
195+
version = 0
196+
197+
[[published]]
198+
source = { local = "/workspace/builder-scaffold/move-contracts/smart_gate_extension" }
199+
published-at = "0xEXT"
200+
original-id = "0xEXT"
201+
version = 0
202+
`), 0600))
203+
204+
output := `{"objectChanges":[{"type":"created","objectType":"0x::builder::ExtensionConfig","objectId":"0xCFG"}]}`
205+
require.NoError(t, writePublishedIDs(workspace, output, pubfile))
206+
207+
envData, err := os.ReadFile(filepath.Join(builderDir, ".env"))
208+
require.NoError(t, err)
209+
assert.Contains(t, string(envData), "BUILDER_PACKAGE_ID=0xEXT")
210+
assert.Contains(t, string(envData), "EXTENSION_CONFIG_ID=0xCFG")
211+
}
212+
156213
// ── buildPublishCmd ────────────────────────────────────────────────
157214

158215
func TestBuildPublishCmd_Localnet(t *testing.T) {
@@ -163,11 +220,12 @@ func TestBuildPublishCmd_Localnet(t *testing.T) {
163220
pubFile := filepath.Join(pubDir, "Pub.extension.toml")
164221
require.NoError(t, os.WriteFile(pubFile, []byte("old"), 0600))
165222

166-
cmd, err := buildPublishCmd(nil, tmp, "localnet", "/workspace/contracts/my_ext")
223+
cmd, pubfilePath, err := buildPublishCmd(nil, tmp, "localnet", "/workspace/contracts/my_ext")
167224
require.NoError(t, err)
168225
assert.Contains(t, cmd, "sui client test-publish")
169226
assert.Contains(t, cmd, "--pubfile-path /workspace/builder-scaffold/deployments/localnet/Pub.extension.toml")
170227
assert.Contains(t, cmd, "/workspace/contracts/my_ext")
228+
assert.Equal(t, pubFile, pubfilePath)
171229
// The stale file should have been removed
172230
_, statErr := os.Stat(pubFile)
173231
assert.True(t, os.IsNotExist(statErr))
@@ -177,24 +235,30 @@ func TestBuildPublishCmd_LocalnetWithExistingWorldPubfile(t *testing.T) {
177235
tmp := t.TempDir()
178236
pubDir := filepath.Join(tmp, "builder-scaffold", "deployments", "localnet")
179237
require.NoError(t, os.MkdirAll(pubDir, 0750))
180-
require.NoError(t, os.WriteFile(filepath.Join(pubDir, "Pub.localnet.toml"), []byte("world"), 0600))
238+
worldPubfile := filepath.Join(pubDir, "Pub.localnet.toml")
239+
require.NoError(t, os.WriteFile(worldPubfile, []byte("world"), 0600))
181240

182-
cmd, err := buildPublishCmd(nil, tmp, "localnet", "/workspace/contracts/my_ext")
241+
cmd, pubfilePath, err := buildPublishCmd(nil, tmp, "localnet", "/workspace/contracts/my_ext")
183242
require.NoError(t, err)
184243
assert.Contains(t, cmd, "sui client test-publish")
185-
assert.Contains(t, cmd, "--pubfile-path /workspace/builder-scaffold/deployments/localnet/Pub.localnet.toml")
244+
assert.Contains(t, cmd, "--pubfile-path /workspace/builder-scaffold/deployments/localnet/Pub.extension.toml")
186245
assert.NotContains(t, cmd, "sui client publish --pubfile-path")
246+
assert.Equal(t, filepath.Join(pubDir, "Pub.extension.toml"), pubfilePath)
247+
seededPubfile, readErr := os.ReadFile(pubfilePath)
248+
require.NoError(t, readErr)
249+
assert.Equal(t, "world", string(seededPubfile))
187250
}
188251

189252
func TestBuildPublishCmd_Testnet(t *testing.T) {
190-
cmd, err := buildPublishCmd(nil, "/ws", "testnet", "/workspace/contracts/ext")
253+
cmd, pubfilePath, err := buildPublishCmd(nil, "/ws", "testnet", "/workspace/contracts/ext")
191254
require.NoError(t, err)
192255
assert.Contains(t, cmd, "sui client publish")
193256
assert.Contains(t, cmd, "--json")
257+
assert.Empty(t, pubfilePath)
194258
}
195259

196260
func TestBuildPublishCmd_UnsupportedNetwork(t *testing.T) {
197-
_, err := buildPublishCmd(nil, "/ws", "mainnet", "/dir")
261+
_, _, err := buildPublishCmd(nil, "/ws", "mainnet", "/dir")
198262
assert.Error(t, err)
199263
assert.Contains(t, err.Error(), "unsupported network")
200264
}

pkg/builder/publish.go

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func PublishExtension(c container.ContainerClient, workspace string, network str
7676

7777
ui.Info.Printf("Executing publish inside container at %s...\n", candidate.ContainerPath)
7878

79-
publishCmd, err := buildPublishCmd(c, workspace, network, candidate.ContainerPath)
79+
publishCmd, pubfilePath, err := buildPublishCmd(c, workspace, network, candidate.ContainerPath)
8080
if err != nil {
8181
return err
8282
}
@@ -91,7 +91,7 @@ func PublishExtension(c container.ContainerClient, workspace string, network str
9191
return fmt.Errorf("publish command failed: %w", err)
9292
}
9393

94-
return writePublishedIDs(workspace, output)
94+
return writePublishedIDs(workspace, output, pubfilePath)
9595
}
9696

9797
func resolvePublishContractDir(workspace string) (PublishCandidate, error) {
@@ -225,9 +225,9 @@ func isExtensionManifest(manifestPath string) (bool, error) {
225225
return strings.Contains(string(manifestContent), worldDependencyMarker), nil
226226
}
227227

228-
// buildPublishCmd constructs the sui publish command and, for localnet, uses
229-
// test-publish with pubfiles so that re-running is idempotent.
230-
func buildPublishCmd(c container.ContainerClient, workspace, network, containerContractDir string) (string, error) {
228+
// buildPublishCmd constructs the sui publish command and, for localnet, returns
229+
// the pubfile updated by test-publish so IDs can be recovered from it when needed.
230+
func buildPublishCmd(c container.ContainerClient, workspace, network, containerContractDir string) (string, string, error) {
231231
switch network {
232232
case "localnet":
233233
// Check if we have an existing world publication file to use as a dependency.
@@ -256,40 +256,52 @@ func buildPublishCmd(c container.ContainerClient, workspace, network, containerC
256256

257257
if foundPub != "" {
258258
ui.Info.Printf("Found existing world publication (%s); using it as a dependency.\n", foundPub)
259+
pubFile := filepath.Join(workspace, "builder-scaffold", "deployments", network, "Pub.extension.toml")
260+
if err := copyFile(foundPath, pubFile); err != nil {
261+
return "", "", fmt.Errorf("failed to seed extension pubfile from %s: %w", foundPub, err)
262+
}
259263
return fmt.Sprintf(
260-
"cd %s && sui client test-publish --pubfile-path /workspace/builder-scaffold/deployments/localnet/%s --build-env testnet --json",
261-
containerContractDir, foundPub,
262-
), nil
264+
"cd %s && sui client test-publish --pubfile-path /workspace/builder-scaffold/deployments/localnet/Pub.extension.toml --build-env testnet --json",
265+
containerContractDir,
266+
), pubFile, nil
263267
}
264268

265269
// Fallback to full publish if no existing world publication is found
266270
pubFile := filepath.Join(workspace, "builder-scaffold", "deployments", network, "Pub.extension.toml")
267271
if err := os.Remove(pubFile); err != nil && !os.IsNotExist(err) {
268-
return "", fmt.Errorf("failed to remove previous publish file: %w", err)
272+
return "", "", fmt.Errorf("failed to remove previous publish file: %w", err)
269273
}
270274
return fmt.Sprintf(
271275
"cd %s && sui client test-publish --with-unpublished-dependencies --build-env testnet --pubfile-path /workspace/builder-scaffold/deployments/localnet/Pub.extension.toml --json",
272276
containerContractDir,
273-
), nil
277+
), pubFile, nil
274278

275279
case "testnet":
276280
return fmt.Sprintf(
277281
"cd %s && sui client publish --with-unpublished-dependencies --build-env testnet --json",
278282
containerContractDir,
279-
), nil
283+
), "", nil
280284

281285
default:
282-
return "", fmt.Errorf("unsupported network %s", network)
286+
return "", "", fmt.Errorf("unsupported network %s", network)
283287
}
284288
}
285289

286290
// writePublishedIDs parses the publish command JSON output and writes the discovered
287291
// package and config IDs into builder-scaffold/.env.
288-
func writePublishedIDs(workspace, output string) error {
292+
func writePublishedIDs(workspace, output, pubfilePath string) error {
289293
builderPackageID, extensionConfigID, parseErr := extractPublishIDs(output)
290294
if parseErr != nil {
291295
ui.Warn.Printf("Could not parse publish output as JSON: %v\n", parseErr)
292296
}
297+
if builderPackageID == "" && pubfilePath != "" {
298+
pubfilePackageID, err := getLastPublishedAt(pubfilePath)
299+
if err != nil {
300+
ui.Warn.Printf("Could not extract BUILDER_PACKAGE_ID from %s: %v\n", pubfilePath, err)
301+
} else {
302+
builderPackageID = pubfilePackageID
303+
}
304+
}
293305

294306
if builderPackageID == "" {
295307
ui.Warn.Println("Could not automatically extract BUILDER_PACKAGE_ID. Please set it manually in builder-scaffold/.env")
@@ -520,3 +532,20 @@ func getPubfileChainID(pubfilePath string) (string, error) {
520532
}
521533
return "", fmt.Errorf("chain-id not found in %s", pubfilePath)
522534
}
535+
536+
func getLastPublishedAt(pubfilePath string) (string, error) {
537+
content, err := os.ReadFile(pubfilePath) // #nosec G304 -- path is constructed in caller from workspace-local paths
538+
if err != nil {
539+
return "", err
540+
}
541+
re := regexp.MustCompile(`published-at\s*=\s*"([^"]+)"`)
542+
matches := re.FindAllStringSubmatch(string(content), -1)
543+
if len(matches) == 0 {
544+
return "", fmt.Errorf("published-at not found in %s", pubfilePath)
545+
}
546+
last := matches[len(matches)-1]
547+
if len(last) < 2 || last[1] == "" {
548+
return "", fmt.Errorf("published-at not found in %s", pubfilePath)
549+
}
550+
return last[1], nil
551+
}

0 commit comments

Comments
 (0)