create-principles-disciple 1.131.0 → 1.131.1
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.
|
@@ -101,6 +101,40 @@ function logLegacyBackupMigration(source) {
|
|
|
101
101
|
// ---------------------------------------------------------------------------
|
|
102
102
|
// Core update operations (inline to avoid cross-package import)
|
|
103
103
|
// ---------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* Read the identity of a staged release package.
|
|
106
|
+
*
|
|
107
|
+
* Returns undefined when the file is missing, malformed, or when the package
|
|
108
|
+
* does not self-identify as principles-disciple with a valid semver version
|
|
109
|
+
* (rc-1/rc-2: unknown JSON is validated through guards, never a type
|
|
110
|
+
* assertion).
|
|
111
|
+
*
|
|
112
|
+
* Refusing an identity-less staged package BEFORE any production copy turns
|
|
113
|
+
* silent runtime corruption into a loud, structured error (rc-9). Regression
|
|
114
|
+
* from 2026-09-03: a stub tarball carrying only a fake version with no
|
|
115
|
+
* package name was copied into the real ~/.pd/runtime, which made the update
|
|
116
|
+
* page report a false "already latest" and blocked every future update. A
|
|
117
|
+
* real principles-disciple / create-principles-disciple tarball always
|
|
118
|
+
* carries name + valid semver.
|
|
119
|
+
*/
|
|
120
|
+
function readStagedPackageIdentity(pkgPath) {
|
|
121
|
+
try {
|
|
122
|
+
const raw = fs.readFileSync(pkgPath, 'utf-8');
|
|
123
|
+
const parsed = JSON.parse(raw);
|
|
124
|
+
if (!isRecord(parsed))
|
|
125
|
+
return undefined;
|
|
126
|
+
if (parsed.name !== 'principles-disciple')
|
|
127
|
+
return undefined;
|
|
128
|
+
if (typeof parsed.version !== 'string' || semver.valid(parsed.version) === null)
|
|
129
|
+
return undefined;
|
|
130
|
+
return { name: parsed.name, version: parsed.version };
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const STAGED_PACKAGE_REFUSAL_MESSAGE = 'Update source is not a valid principles-disciple release (package missing identity or valid version).';
|
|
137
|
+
const STAGED_PACKAGE_REFUSAL_NEXT_ACTION = 'Try the update again later, or run the official installer (npx create-principles-disciple) to repair PD.';
|
|
104
138
|
/**
|
|
105
139
|
* Recursively copy a directory tree.
|
|
106
140
|
*
|
|
@@ -496,6 +530,30 @@ async function doApplyUpdate(options, workspaceDir) {
|
|
|
496
530
|
// not support GNU tar's --force-local, so relative path is the universal fix.
|
|
497
531
|
execFileSync('tar', ['xzf', 'package.tgz', '--strip-components=1'], { cwd: tempDir, stdio: 'pipe' });
|
|
498
532
|
fs.unlinkSync(tarballPath);
|
|
533
|
+
// 3.5 Staged-package identity guard (2026-09-03 regression): refuse a
|
|
534
|
+
// tarball that does not name itself principles-disciple with a valid
|
|
535
|
+
// version BEFORE any production byte is copied — the incident stub
|
|
536
|
+
// carried no name. A real release always carries both.
|
|
537
|
+
if (readStagedPackageIdentity(path.join(tempDir, 'package.json')) === undefined) {
|
|
538
|
+
if (fs.existsSync(tempDir))
|
|
539
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
540
|
+
if (backupPath && fs.existsSync(backupPath))
|
|
541
|
+
fs.rmSync(backupPath, { recursive: true, force: true });
|
|
542
|
+
appendUpdateHistory(workspaceDir, {
|
|
543
|
+
fromVersion,
|
|
544
|
+
toVersion: toVersion ?? 'unknown',
|
|
545
|
+
success: false,
|
|
546
|
+
kind: 'refusal',
|
|
547
|
+
reason: 'staged_package_invalid',
|
|
548
|
+
nextAction: STAGED_PACKAGE_REFUSAL_NEXT_ACTION,
|
|
549
|
+
});
|
|
550
|
+
return {
|
|
551
|
+
success: false,
|
|
552
|
+
message: STAGED_PACKAGE_REFUSAL_MESSAGE,
|
|
553
|
+
reason: 'staged_package_invalid',
|
|
554
|
+
nextAction: STAGED_PACKAGE_REFUSAL_NEXT_ACTION,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
499
557
|
// 4. Compute diff and apply.
|
|
500
558
|
// Fix 3: we ONLY apply modified + added files. We deliberately skip ALL
|
|
501
559
|
// deletions — the tarball (principles-disciple) only contains dist/,
|
|
@@ -859,6 +917,29 @@ async function doInlineFullUpdate(workspaceDir) {
|
|
|
859
917
|
// gateway is stopped or any production file is copied.
|
|
860
918
|
const newPkgPath = path.join(tempDir, 'plugin', 'package.json');
|
|
861
919
|
const stagedVersion = readCurrentVersion(path.join(tempDir, 'plugin'));
|
|
920
|
+
// Staged-package identity guard (2026-09-03 regression): the staged plugin
|
|
921
|
+
// must self-identify as principles-disciple with a valid version before
|
|
922
|
+
// the gateway is stopped or any production file is copied. The incident
|
|
923
|
+
// stub carried only a fake version with no package name.
|
|
924
|
+
if (readStagedPackageIdentity(path.join(tempDir, 'plugin', 'package.json')) === undefined) {
|
|
925
|
+
if (tempDir && fs.existsSync(tempDir))
|
|
926
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
927
|
+
appendUpdateHistory(workspaceDir, {
|
|
928
|
+
fromVersion,
|
|
929
|
+
toVersion: stagedVersion ?? 'unknown',
|
|
930
|
+
success: false,
|
|
931
|
+
kind: 'refusal',
|
|
932
|
+
reason: 'staged_package_invalid',
|
|
933
|
+
nextAction: STAGED_PACKAGE_REFUSAL_NEXT_ACTION,
|
|
934
|
+
});
|
|
935
|
+
return {
|
|
936
|
+
success: false,
|
|
937
|
+
message: STAGED_PACKAGE_REFUSAL_MESSAGE,
|
|
938
|
+
reason: 'staged_package_invalid',
|
|
939
|
+
nextAction: STAGED_PACKAGE_REFUSAL_NEXT_ACTION,
|
|
940
|
+
requiresRestart: false,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
862
943
|
const progressed = fromVersion !== 'unknown' &&
|
|
863
944
|
stagedVersion !== undefined &&
|
|
864
945
|
semver.valid(fromVersion) !== null &&
|