package: reject duplicate project manifest versions
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- `claro package doctor` now rejects duplicate `manifest-version:` fields in `claro.project` instead of accepting the first value and overlooking ambiguous project metadata.
|
||||
|
||||
- `claro package doctor` now rejects duplicate package entries in `claro.lock` instead of allowing ambiguous lockfile data.
|
||||
- `claro package doctor` now rejects duplicate `name:` entries in a package manifest instead of accepting ambiguous package identity data.
|
||||
- `claro package doctor` now rejects duplicate `checksum:` entries in a package manifest instead of accepting ambiguous integrity data.
|
||||
|
||||
@@ -301,7 +301,9 @@ It also compares the complete `checksum:` field, so extra or trailing checksum
|
||||
text, or a duplicate checksum field, is rejected as `BAD package checksum`.
|
||||
Package manifests must contain exactly one `version: 1` field; duplicate or
|
||||
prefix-matching values such as `version: 10` are rejected as `BAD package version`.
|
||||
The project manifest must also contain exactly one non-empty `name:` field;
|
||||
The project manifest must contain exactly one `manifest-version: 1` field; duplicate
|
||||
or prefix-matching values such as `manifest-version: 10` are rejected as
|
||||
`BAD project manifest version: expected 1`. It must also contain exactly one non-empty `name:` field;
|
||||
otherwise doctor reports `BAD project manifest name: expected a non-empty name`.
|
||||
|
||||
Starter projects created with `claro new MyProject` use the same `manifest-version: 1` and `lock-version: 1` headers as `claro package init`, so the first project files match the package maintenance tools.
|
||||
|
||||
@@ -111,6 +111,7 @@ Ready now:
|
||||
- package manifest `name:` entries must also be unique; `claro package doctor` reports `BAD package manifest name` instead of accepting ambiguous package identity data
|
||||
- package manifest `checksum:` entries must also be unique; `claro package doctor` reports `BAD package checksum` instead of accepting ambiguous integrity data
|
||||
- package manifest `version:` entries must also be unique and must contain exactly `1`; `claro package doctor` reports `BAD package version` instead of accepting ambiguous package metadata
|
||||
- project manifests must contain exactly one `manifest-version: 1` field; duplicate or prefix-matching values are rejected with `BAD project manifest version: expected 1`
|
||||
- project manifests must contain exactly one non-empty `name:` field; `claro package doctor` reports `BAD project manifest name: expected a non-empty name` when the project identity is missing or blank
|
||||
|
||||
Still needed:
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ See `CURRENT_STATUS.md` for the detailed feature matrix.
|
||||
2. Keep examples aligned with the modern simple syntax (`END`, `DO`, short `SET`, short `ASK`) while documenting older compatibility forms separately.
|
||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current Forgejo/Gitea CI runs the documented release gates (`claro validate`, typecheck diagnostics validation, version convention validation, package security validation, and CI workflow coverage validation). Current package safety validation covers safe project creation, rejecting unsafe project names during `claro new`, safe manifest/lockfile creation, rejecting unsafe package names during `package add`, detecting unsafe package names already present in `claro.project` during `package doctor`, refusing to write lockfile data for unsafe package names during `package lock`, making `package list` fail instead of displaying unsafe package entries as normal package names, making `package init` fail instead of reporting the project ready when unsafe package names are already present, making `package add` fail before changing files when unsafe package names are already present, making `package remove` fail instead of reporting success when unsafe package names remain during lockfile refresh, allowing `package remove` to remove an exact unsafe package entry so learners can repair a bad `claro.project`, making `package doctor` reject stale lockfile checksums for listed packages, and making `package doctor` reject lockfile package entries that are not listed in `claro.project`. Current function validation covers correct checked calls, wrong-type arguments, missing checked-argument diagnostics, missing unchecked-argument diagnostics for simple functions in both modern `DO greet` and empty compatibility `CALL greet WITH` forms, extra-argument diagnostics, and beginner-facing unknown-function diagnostics for mistyped modern `DO` and compatibility `CALL ... WITH` calls; current object-method parameter validation covers correct modern `DO object.method ...` and compatibility `CALL object.method WITH ...` checked calls, wrong-type diagnostics for both call forms, checked methods after another method in the same class, missing and extra checked-argument diagnostics including the extra-argument case for a checked method after another method in the same class, missing unchecked-argument diagnostics for simple object methods, missing-object guidance when `DO player.method ...` or compatibility `CALL object.method WITH ...` appears before `NEW` even if the method name is also wrong, and class-specific unknown-method diagnostics for both modern and compatibility calls when a learner calls a method the class does not declare; current object-field validation covers direct NUMBER/TEXT/YESNO field-assignment positives, direct object-field `CHECK TYPE` metadata positives for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO field metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, direct wrong-type field assignment diagnostics, field collection when a `HAS` field appears after a simple method, direct unknown-field diagnostics for NUMBER/TEXT/YESNO values, a beginner-facing fallback for unknown fields assigned from expressions whose type is not inferable yet, and missing-object diagnostics for both `SET object.field value` and `CHECK TYPE object.field IS TYPE` before `NEW`.
|
||||
3a. Keep package validation honest by checking that the project manifest has one non-empty project name, and that the project and each listed package manifest declare the exact supported manifest version, package version, local source, and expected package name.
|
||||
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/version/checksum mismatches, missing manifests, duplicate project packages, duplicate lock packages, duplicate package manifest names, versions, and checksums, and lockfile errors remain release blockers.
|
||||
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/version/checksum mismatches, missing manifests, duplicate project manifest versions and packages, duplicate lock packages, duplicate package manifest names, versions, and checksums, and lockfile errors remain release blockers.
|
||||
4. Add small examples for each foundation feature before adding bigger syntax.
|
||||
|
||||
## Complete-platform milestones
|
||||
|
||||
+1
-1
@@ -638,7 +638,7 @@ static void create_package_folder(const char *name){ char path[512], meta[768],
|
||||
static int list_project_packages(void){ char *txt=read_file_text("claro.project"); char *p; int count=0; printf("Packages in claro.project:\n"); if(!txt){ printf(" (no claro.project yet)\n"); return 0; } if(!project_package_names_safe()){ free(txt); return 1; } p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(*pkg){ printf(" %s\n",pkg); count++; } } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } if(!count) printf(" (none)\n"); free(txt); return 0; }
|
||||
static int package_lock_checksum_ok(const char *name){ char *txt=read_file_text("claro.lock"); char *p; int in_pkg=0, ok=0; char expected[32]; if(!txt) return 0; package_checksum(name,expected,sizeof(expected)); p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); in_pkg=(strcmp(pkg,name)==0); } else if(in_pkg&&strnicmp2(t,"checksum:",9)==0){ char *sum=trim_inplace(t+9); ok=(strcmp(sum,expected)==0); break; } } if(save){ *p=save; p++; } while(*p=='\n'||*p=='\r') p++; } free(txt); return ok; }
|
||||
static int lock_packages_match_project(void){ char *txt=read_file_text("claro.lock"); char *p; char seen[128][65]; int seen_count=0; int ok=1; if(!txt) return 1; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); int i; if(*pkg&&!package_name_safe(pkg)){ printf(" BAD lock package name: %s\n",pkg); ok=0; } else if(*pkg){ for(i=0;i<seen_count;i++){ if(strcmp(seen[i],pkg)==0){ printf(" DUPLICATE lock package: %s\n",pkg); ok=0; break; } } if(seen_count<128) snprintf(seen[seen_count++],sizeof(seen[0]),"%s",pkg); if(!package_has_name(pkg)){ printf(" BAD lock package not in claro.project: %s\n",pkg); ok=0; } } } } if(save){ *p=save; p++; } while(*p=='\n'||*p=='\r') p++; } free(txt); return ok; }
|
||||
static int package_manifest_version_matches(const char *text){ const char *p=text; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=17&&strncmp(line,"manifest-version:",17)==0){ const char *value=line+17; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; return (size_t)(end-value)==1&&value[0]=='1'; } while(*p=='\n'||*p=='\r') p++; } return 0; }
|
||||
static int package_manifest_version_matches(const char *text){ const char *p=text; int fields=0; int matches=0; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=17&&strncmp(line,"manifest-version:",17)==0){ const char *value=line+17; fields++; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; if((size_t)(end-value)==1&&value[0]=='1') matches++; } while(*p=='\n'||*p=='\r') p++; } return fields==1&&matches==1; }
|
||||
static int package_project_name_valid(const char *text){ const char *p=text; int fields=0; int nonempty=0; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=5&&strncmp(line,"name:",5)==0){ const char *value=line+5; fields++; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; if(value<end) nonempty++; } while(*p=='\n'||*p=='\r') p++; } return fields==1&&nonempty==1; }
|
||||
static int package_manifest_name_matches(const char *text,const char *name){ const char *p=text; size_t n=strlen(name); int fields=0; int matches=0; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=5&&strncmp(line,"name:",5)==0){ const char *value=line+5; fields++; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; if((size_t)(end-value)==n&&strncmp(value,name,n)==0) matches++; } while(*p=='\n'||*p=='\r') p++; } return fields==1&&matches==1; }
|
||||
static int package_manifest_version_value_matches(const char *text){ const char *p=text; int fields=0; int matches=0; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=8&&strncmp(line,"version:",8)==0){ const char *value=line+8; fields++; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; if((size_t)(end-value)==1&&value[0]=='1') matches++; } while(*p=='\n'||*p=='\r') p++; } return fields==1&&matches==1; }
|
||||
|
||||
@@ -234,6 +234,14 @@ def main():
|
||||
if "BAD project manifest name: expected a non-empty name" not in out:
|
||||
fail(f"Package project-name diagnostic was unclear:\n{out}")
|
||||
(work / "claro.project").write_text(project, encoding="utf-8")
|
||||
duplicate_project_manifest = project_with_package + "manifest-version: 1\n"
|
||||
(work / "claro.project").write_text(duplicate_project_manifest, encoding="utf-8")
|
||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
||||
if rc == 0:
|
||||
fail("package doctor must reject duplicate project manifest versions")
|
||||
if "BAD project manifest version: expected 1" not in out:
|
||||
fail(f"Package duplicate project manifest-version diagnostic was unclear:\n{out}")
|
||||
(work / "claro.project").write_text(project, encoding="utf-8")
|
||||
|
||||
(work / "claro.project").write_text(
|
||||
project + "package: math-tools\npackage: math-tools\n",
|
||||
|
||||
Reference in New Issue
Block a user