@stage5/lumine 0.2.13 → 0.2.14
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 +8 -1
- package/lib/api.js +4 -0
- package/lib/commands.js +41 -6
- package/lib/constants.js +6 -0
- package/lib/workspace.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,14 @@ to pull source files into a read-only reference folder, or
|
|
|
40
40
|
`lumine fork <build-url-or-id>` to create your own editable fork and pull it
|
|
41
41
|
locally.
|
|
42
42
|
|
|
43
|
-
After editing pulled files, run `lumine save` from that folder.
|
|
43
|
+
After editing pulled files, run `lumine save` from that folder. Workspaces record
|
|
44
|
+
a `filesHash` in `.twinkle/lumine-project.json` when you pull or save; the next
|
|
45
|
+
save sends that hash so the server can reject a stale checkout instead of
|
|
46
|
+
silently overwriting newer files. Saves from a folder with no `filesHash` are
|
|
47
|
+
refused unless you pass `--force` (intentional overwrite). Prefer
|
|
48
|
+
`lumine pull` to resync before saving if the folder might be out of date.
|
|
49
|
+
|
|
50
|
+
The CLI saves
|
|
44
51
|
through Twinkle's normal workspace project-file route, creates a project artifact
|
|
45
52
|
version, records the same save metadata, and marks public builds as having
|
|
46
53
|
unpublished changes. For projects you own, run `lumine launch` to publish the
|
package/lib/api.js
CHANGED
|
@@ -139,6 +139,7 @@ export async function saveProjectFiles({
|
|
|
139
139
|
files,
|
|
140
140
|
summary,
|
|
141
141
|
baseFilesHash,
|
|
142
|
+
force = false,
|
|
142
143
|
}) {
|
|
143
144
|
return await requestJson({
|
|
144
145
|
method: "PUT",
|
|
@@ -152,6 +153,9 @@ export async function saveProjectFiles({
|
|
|
152
153
|
// reject it instead of silently rewinding newer state (e.g. a branch
|
|
153
154
|
// merged into main after our pull).
|
|
154
155
|
...(baseFilesHash ? { baseFilesHash } : {}),
|
|
156
|
+
// Intentional overwrite of a non-empty project without a base claim.
|
|
157
|
+
// Required when the workspace has no filesHash (old/stale checkout).
|
|
158
|
+
...(force ? { force: true } : {}),
|
|
155
159
|
},
|
|
156
160
|
timeoutMs: options.timeoutMs,
|
|
157
161
|
});
|
package/lib/commands.js
CHANGED
|
@@ -509,13 +509,36 @@ export async function save(options) {
|
|
|
509
509
|
const files = await collectProjectFiles(dir);
|
|
510
510
|
assertProjectFilesWithinLimits(files);
|
|
511
511
|
// Only claim a base snapshot when this workspace was pulled from the same
|
|
512
|
-
// build we are saving to
|
|
512
|
+
// build we are saving to. Missing filesHash used to mean "unguarded save",
|
|
513
|
+
// which let stale checkouts silently rewind newer server versions — refuse
|
|
514
|
+
// that path unless --force is explicit.
|
|
515
|
+
const metadataBuildId = Number(localProject?.metadata?.buildId || 0);
|
|
516
|
+
const metadataFilesHash =
|
|
517
|
+
typeof localProject?.metadata?.filesHash === "string" &&
|
|
518
|
+
localProject.metadata.filesHash.trim()
|
|
519
|
+
? localProject.metadata.filesHash.trim()
|
|
520
|
+
: null;
|
|
513
521
|
const baseFilesHash =
|
|
514
|
-
!options.force &&
|
|
515
|
-
|
|
516
|
-
typeof localProject?.metadata?.filesHash === "string"
|
|
517
|
-
? localProject.metadata.filesHash
|
|
522
|
+
!options.force && metadataBuildId === Number(buildId) && metadataFilesHash
|
|
523
|
+
? metadataFilesHash
|
|
518
524
|
: null;
|
|
525
|
+
if (!options.force && !baseFilesHash) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
[
|
|
528
|
+
"Save refused: this workspace has no filesHash (server snapshot token).",
|
|
529
|
+
"That usually means an old or stale checkout that never recorded what",
|
|
530
|
+
"it was based on — saving it would overwrite the server without a guard.",
|
|
531
|
+
"Run `lumine pull` to sync with the server (local edits are preserved),",
|
|
532
|
+
"then save again. To intentionally overwrite the server with these",
|
|
533
|
+
"local files, re-run with --force.",
|
|
534
|
+
].join("\n"),
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
if (options.force && !baseFilesHash) {
|
|
538
|
+
console.error(
|
|
539
|
+
"lumine: warning — saving with --force and no filesHash; this overwrites the server project with local files and skips the stale-workspace guard.",
|
|
540
|
+
);
|
|
541
|
+
}
|
|
519
542
|
let result;
|
|
520
543
|
try {
|
|
521
544
|
result = await saveProjectFiles({
|
|
@@ -525,6 +548,7 @@ export async function save(options) {
|
|
|
525
548
|
files,
|
|
526
549
|
summary: options.summary || DEFAULT_SAVE_SUMMARY,
|
|
527
550
|
baseFilesHash,
|
|
551
|
+
force: Boolean(options.force),
|
|
528
552
|
});
|
|
529
553
|
} catch (error) {
|
|
530
554
|
if (error?.data?.code === "build_project_files_stale") {
|
|
@@ -537,6 +561,17 @@ export async function save(options) {
|
|
|
537
561
|
].join("\n"),
|
|
538
562
|
);
|
|
539
563
|
}
|
|
564
|
+
if (error?.data?.code === "build_project_files_base_required") {
|
|
565
|
+
throw new Error(
|
|
566
|
+
[
|
|
567
|
+
"Save rejected: the server requires a filesHash base for this project",
|
|
568
|
+
"(non-empty projects cannot be saved from a workspace that never",
|
|
569
|
+
"recorded its server snapshot).",
|
|
570
|
+
"Run `lumine pull` to establish a base, then save again,",
|
|
571
|
+
"or re-run with --force to overwrite deliberately.",
|
|
572
|
+
].join("\n"),
|
|
573
|
+
);
|
|
574
|
+
}
|
|
540
575
|
throw error;
|
|
541
576
|
}
|
|
542
577
|
build = result.build ||
|
|
@@ -1954,7 +1989,7 @@ Options:
|
|
|
1954
1989
|
--description <text> Optional New Build description
|
|
1955
1990
|
--no-description Skip the New Build description prompt
|
|
1956
1991
|
--summary <text> Save summary
|
|
1957
|
-
--force
|
|
1992
|
+
--force Overwrite server files even if this workspace is stale or missing filesHash
|
|
1958
1993
|
--search <text> Search public open-source Builds
|
|
1959
1994
|
--sort <sort> Sort open-source Builds: forks, popular, recent
|
|
1960
1995
|
--publish Publish after saving
|
package/lib/constants.js
CHANGED
|
@@ -143,6 +143,12 @@ Lumine CLI as the source of truth for saving this workspace back to Twinkle.
|
|
|
143
143
|
|
|
144
144
|
- Edit only project files in this workspace.
|
|
145
145
|
- Keep /index.html or /index.htm as the entry file.
|
|
146
|
+
- Before editing an existing project, confirm \`.twinkle/lumine-project.json\`
|
|
147
|
+
has a \`filesHash\` and matches the build you intend. If the folder may be
|
|
148
|
+
stale (old pull, missing files, fewer files than last known save), run
|
|
149
|
+
\`lumine pull\` first — never \`lumine save\` from a guessed/old checkout.
|
|
150
|
+
Saves without \`filesHash\` are refused (use \`--force\` only for deliberate
|
|
151
|
+
overwrites). A stale save can delete newer server files.
|
|
146
152
|
- Run lumine save from this folder after edits, with a short summary:
|
|
147
153
|
|
|
148
154
|
\`\`\`bash
|
package/lib/workspace.js
CHANGED
|
@@ -597,7 +597,8 @@ export async function writeProjectMetadata({
|
|
|
597
597
|
lastSavedAt,
|
|
598
598
|
// Server-issued hash of the project files this workspace is based on.
|
|
599
599
|
// `lumine save` sends it back so a stale workspace cannot silently
|
|
600
|
-
// rewind newer server state
|
|
600
|
+
// rewind newer server state. Missing/null refuses save unless --force
|
|
601
|
+
// (server also rejects unguarded saves onto non-empty projects).
|
|
601
602
|
filesHash: typeof filesHash === "string" ? filesHash : null,
|
|
602
603
|
},
|
|
603
604
|
null,
|