@ai-plugin-marketplace/core 0.2.0 → 0.4.0
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/dist/core.d.ts +70 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/init-template.d.ts +24 -6
- package/dist/pipeline/init-template.d.ts.map +1 -1
- package/dist/pipeline/init-template.js +131 -33
- package/dist/pipeline/init-template.js.map +1 -1
- package/dist/pipeline/init.d.ts +19 -14
- package/dist/pipeline/init.d.ts.map +1 -1
- package/dist/pipeline/init.js +68 -16
- package/dist/pipeline/init.js.map +1 -1
- package/dist/pipeline/operations.d.ts +11 -1
- package/dist/pipeline/operations.d.ts.map +1 -1
- package/dist/pipeline/operations.js +16 -0
- package/dist/pipeline/operations.js.map +1 -1
- package/dist/pipeline/scaffold-refresh.d.ts +80 -0
- package/dist/pipeline/scaffold-refresh.d.ts.map +1 -0
- package/dist/pipeline/scaffold-refresh.js +152 -0
- package/dist/pipeline/scaffold-refresh.js.map +1 -0
- package/dist/pipeline/types.d.ts +57 -1
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/pipeline/validate.d.ts +12 -0
- package/dist/pipeline/validate.d.ts.map +1 -1
- package/dist/pipeline/validate.js +103 -1
- package/dist/pipeline/validate.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @see docs/specs/architecture.md §8.1
|
|
11
11
|
*/
|
|
12
|
-
import type { BuildOptions, BuildResult, InitOptions, MigrateOptions, MigrateResult, ScaffoldOptions, SupportReport, TargetId, ValidateOptions, ValidationResult } from './types.js';
|
|
12
|
+
import type { BuildOptions, BuildResult, InitOptions, MigrateOptions, MigrateResult, RefreshOptions, RefreshOutcome, ScaffoldOptions, SupportReport, TargetId, ValidateOptions, ValidationResult } from './types.js';
|
|
13
13
|
/**
|
|
14
14
|
* Build a single plugin or every plugin under a repo root. `path` may be a plugin directory
|
|
15
15
|
* (contains `aipm.config.ts`) or a repo root (contains `plugins/`); the orchestrator detects
|
|
@@ -34,6 +34,16 @@ export declare function validate(targetPath: string, opts?: ValidateOptions): Pr
|
|
|
34
34
|
* @public
|
|
35
35
|
*/
|
|
36
36
|
export declare function init(targetDir: string, opts?: InitOptions): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Refresh the toolkit-owned scaffold files (CI workflow, `.gitignore`) of an existing marketplace
|
|
39
|
+
* repo at `targetDir` to match the installed tooling — the upgrade path after
|
|
40
|
+
* `pnpm up @ai-plugin-marketplace/*`. Guarded by the `.aipm/scaffold.json` content-hash sidecar:
|
|
41
|
+
* user-modified files are reported as conflicts and left untouched unless `opts.force` is set.
|
|
42
|
+
* Returns one outcome per managed file; never rejects on conflict.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export declare function refreshScaffold(targetDir: string, opts?: RefreshOptions): Promise<RefreshOutcome[]>;
|
|
37
47
|
/**
|
|
38
48
|
* Scaffold a new plugin under the cwd's configured plugins root (`<cwd>/plugins/<name>` by
|
|
39
49
|
* default, or the relocated `pluginsRoot` from an `aipm.repo.ts`). The plugins directory is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACb,QAAQ,EACR,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CAK3B;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKtF;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAMrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,QAAQ,EAAE,CAEjD"}
|
|
@@ -13,6 +13,7 @@ import * as path from 'node:path';
|
|
|
13
13
|
import { runBuild } from './build.js';
|
|
14
14
|
import { runInit } from './init.js';
|
|
15
15
|
import { loadRepoConfig } from './load-config.js';
|
|
16
|
+
import { runRefreshScaffold } from './scaffold-refresh.js';
|
|
16
17
|
import { runScaffold, runAddTarget, runCheckSupport } from './scaffold.js';
|
|
17
18
|
import { TARGET_IDS } from './types.js';
|
|
18
19
|
import { runValidate } from './validate.js';
|
|
@@ -50,6 +51,21 @@ export function validate(targetPath, opts) {
|
|
|
50
51
|
export function init(targetDir, opts) {
|
|
51
52
|
return runInit(targetDir, opts);
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Refresh the toolkit-owned scaffold files (CI workflow, `.gitignore`) of an existing marketplace
|
|
56
|
+
* repo at `targetDir` to match the installed tooling — the upgrade path after
|
|
57
|
+
* `pnpm up @ai-plugin-marketplace/*`. Guarded by the `.aipm/scaffold.json` content-hash sidecar:
|
|
58
|
+
* user-modified files are reported as conflicts and left untouched unless `opts.force` is set.
|
|
59
|
+
* Returns one outcome per managed file; never rejects on conflict.
|
|
60
|
+
*
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
export function refreshScaffold(targetDir, opts) {
|
|
64
|
+
// Defer into the microtask queue so a synchronous failure in the orchestrator (e.g. an I/O error)
|
|
65
|
+
// surfaces as a rejected promise — matching `refreshScaffold(...).catch(...)` expectations —
|
|
66
|
+
// rather than throwing from this call before the promise exists.
|
|
67
|
+
return Promise.resolve().then(() => runRefreshScaffold(targetDir, opts));
|
|
68
|
+
}
|
|
53
69
|
/**
|
|
54
70
|
* Scaffold a new plugin under the cwd's configured plugins root (`<cwd>/plugins/<name>` by
|
|
55
71
|
* default, or the relocated `pluginsRoot` from an `aipm.repo.ts`). The plugins directory is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAgB5C,sGAAsG;AACtG,SAAS,IAAI;IACX,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,UAAkB,EAAE,IAAmB;IAC3D,OAAO,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,UAAkB,EAAE,IAAsB;IACjE,OAAO,WAAW,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAAC,SAAiB,EAAE,IAAkB;IACxD,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAiB,EACjB,IAAqB;IAErB,kGAAkG;IAClG,6FAA6F;IAC7F,iEAAiE;IACjE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAwB,EAAE;IACrE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC1D,OAAO,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,KAAsB;IAC3D,OAAO,OAAO,CAAC,OAAO,CAAC;QACrB,MAAM,EAAE,sBAAsB;QAC9B,iBAAiB,EAAE,CAAC;QACpB,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,MAAgB;IAC3D,OAAO,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aipm init --refresh` — keep a marketplace repo's **toolkit-owned scaffold files** (the CI
|
|
3
|
+
* workflow and `.gitignore`) in sync with the installed tooling, and serve as the upgrade path for
|
|
4
|
+
* any marketplace repo after `pnpm up @ai-plugin-marketplace/*`.
|
|
5
|
+
*
|
|
6
|
+
* Safety rests on a content-hash sidecar at `.aipm/scaffold.json` (mirroring the root-emission
|
|
7
|
+
* sidecar `.aipm/generated-root.json`): it records a hash of the exact content the toolkit last
|
|
8
|
+
* wrote each managed file. Refresh updates a file only when it is still pristine (matches the
|
|
9
|
+
* recorded hash) or missing; a file the user has edited surfaces as a reported conflict and is left
|
|
10
|
+
* untouched unless `--force` is given. A repo with no sidecar entry bootstraps: an already-in-sync
|
|
11
|
+
* file is adopted silently, a diverged one is a conflict to review.
|
|
12
|
+
*
|
|
13
|
+
* The managed set is deliberately narrow — only the pure tooling-recipe files (see
|
|
14
|
+
* {@link buildManagedScaffoldFiles}). `package.json` (deps are `pnpm up`'s job), `aipm.workspace.ts`
|
|
15
|
+
* (repo identity), `README.md` (authored), plugins, and `aipm build` output are never touched.
|
|
16
|
+
*
|
|
17
|
+
* @see docs/specs/scaffold-refresh-and-upgrade.md
|
|
18
|
+
*/
|
|
19
|
+
import type { RefreshOptions, RefreshOutcome } from './types.js';
|
|
20
|
+
/** A recorded `{ path, hash }` pair: the hash of the content the toolkit last wrote at `path`. */
|
|
21
|
+
interface SidecarEntry {
|
|
22
|
+
path: string;
|
|
23
|
+
hash: string;
|
|
24
|
+
}
|
|
25
|
+
/** Content hash recorded in the sidecar. `sha256-`-prefixed hex over the UTF-8 bytes. */
|
|
26
|
+
export declare function hashScaffoldContent(content: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Serialize a sidecar payload: `{ version, files }` with `files` sorted by path for determinism,
|
|
29
|
+
* 2-space JSON + trailing newline (matching the repo's other generated JSON).
|
|
30
|
+
*/
|
|
31
|
+
export declare function serializeScaffoldSidecar(entries: readonly SidecarEntry[]): string;
|
|
32
|
+
/** Inputs to the pure per-file refresh decision. */
|
|
33
|
+
export interface ManagedFileDecisionInput {
|
|
34
|
+
/** Canonical content the toolkit would render for this file. */
|
|
35
|
+
render: string;
|
|
36
|
+
/** Current on-disk content, or `null` when the file is missing. */
|
|
37
|
+
current: string | null;
|
|
38
|
+
/** Hash recorded in the sidecar for this file, or `null` when untracked. */
|
|
39
|
+
recordedHash: string | null;
|
|
40
|
+
/** Whether `--force` was given. */
|
|
41
|
+
force: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Result of the pure per-file refresh decision. */
|
|
44
|
+
export interface ManagedFileDecision {
|
|
45
|
+
status: RefreshOutcome['status'];
|
|
46
|
+
/** Whether to write `render` to disk. */
|
|
47
|
+
write: boolean;
|
|
48
|
+
/** Hash to record in the new sidecar, or `null` to preserve the prior entry (or stay untracked). */
|
|
49
|
+
recordHash: string | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Decide what to do with one managed scaffold file. Pure — all filesystem state is passed in — so
|
|
53
|
+
* the decision table is unit-testable in isolation.
|
|
54
|
+
*
|
|
55
|
+
* | On-disk state | Result |
|
|
56
|
+
* |-------------------------------------------------|---------------|
|
|
57
|
+
* | missing | `recreated` |
|
|
58
|
+
* | matches the current render | `unchanged` |
|
|
59
|
+
* | differs from render, matches recorded hash | `updated` |
|
|
60
|
+
* | differs from render & recorded (or untracked) | `conflict` * |
|
|
61
|
+
* | …same, with `force` | `overwritten` |
|
|
62
|
+
*
|
|
63
|
+
* \* `unchanged` also adopts an untracked-but-in-sync file by recording its hash.
|
|
64
|
+
*/
|
|
65
|
+
export declare function decideManagedFile(input: ManagedFileDecisionInput): ManagedFileDecision;
|
|
66
|
+
/**
|
|
67
|
+
* Seed `.aipm/scaffold.json` for a freshly scaffolded repo, recording the hash of every managed
|
|
68
|
+
* scaffold file as just written by `aipm init`. Called by {@link runInit} after the seed tree is
|
|
69
|
+
* written so the first `--refresh` has a baseline.
|
|
70
|
+
*/
|
|
71
|
+
export declare function writeScaffoldSidecar(repoRoot: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Refresh the toolkit-owned scaffold files of an existing repo at `repoRoot`, re-rendering each
|
|
74
|
+
* from the installed tooling and updating it in place when safe (see {@link decideManagedFile}).
|
|
75
|
+
* Writes the updated `.aipm/scaffold.json` sidecar and returns one {@link RefreshOutcome} per
|
|
76
|
+
* managed file. Never throws on conflict — conflicts are reported, not failures.
|
|
77
|
+
*/
|
|
78
|
+
export declare function runRefreshScaffold(repoRoot: string, opts?: RefreshOptions): RefreshOutcome[];
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=scaffold-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold-refresh.d.ts","sourceRoot":"","sources":["../../src/pipeline/scaffold-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOjE,kGAAkG;AAClG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAOD,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAGjF;AA+BD,oDAAoD;AACpD,MAAM,WAAW,wBAAwB;IACvC,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mCAAmC;IACnC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjC,yCAAyC;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,oGAAoG;IACpG,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,mBAAmB,CAmBtF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQ3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,cAAc,EAAE,CAmChG"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aipm init --refresh` — keep a marketplace repo's **toolkit-owned scaffold files** (the CI
|
|
3
|
+
* workflow and `.gitignore`) in sync with the installed tooling, and serve as the upgrade path for
|
|
4
|
+
* any marketplace repo after `pnpm up @ai-plugin-marketplace/*`.
|
|
5
|
+
*
|
|
6
|
+
* Safety rests on a content-hash sidecar at `.aipm/scaffold.json` (mirroring the root-emission
|
|
7
|
+
* sidecar `.aipm/generated-root.json`): it records a hash of the exact content the toolkit last
|
|
8
|
+
* wrote each managed file. Refresh updates a file only when it is still pristine (matches the
|
|
9
|
+
* recorded hash) or missing; a file the user has edited surfaces as a reported conflict and is left
|
|
10
|
+
* untouched unless `--force` is given. A repo with no sidecar entry bootstraps: an already-in-sync
|
|
11
|
+
* file is adopted silently, a diverged one is a conflict to review.
|
|
12
|
+
*
|
|
13
|
+
* The managed set is deliberately narrow — only the pure tooling-recipe files (see
|
|
14
|
+
* {@link buildManagedScaffoldFiles}). `package.json` (deps are `pnpm up`'s job), `aipm.workspace.ts`
|
|
15
|
+
* (repo identity), `README.md` (authored), plugins, and `aipm build` output are never touched.
|
|
16
|
+
*
|
|
17
|
+
* @see docs/specs/scaffold-refresh-and-upgrade.md
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import { buildManagedScaffoldFiles } from './init-template.js';
|
|
23
|
+
/** Sidecar location relative to the repo root. */
|
|
24
|
+
const SIDECAR_REL = ['.aipm', 'scaffold.json'];
|
|
25
|
+
/** Schema version of the sidecar payload. */
|
|
26
|
+
const SIDECAR_VERSION = 1;
|
|
27
|
+
/** Absolute path of the scaffold sidecar under `repoRoot`. */
|
|
28
|
+
function sidecarPath(repoRoot) {
|
|
29
|
+
return path.join(repoRoot, ...SIDECAR_REL);
|
|
30
|
+
}
|
|
31
|
+
/** Content hash recorded in the sidecar. `sha256-`-prefixed hex over the UTF-8 bytes. */
|
|
32
|
+
export function hashScaffoldContent(content) {
|
|
33
|
+
return `sha256-${createHash('sha256').update(content, 'utf-8').digest('hex')}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Serialize a sidecar payload: `{ version, files }` with `files` sorted by path for determinism,
|
|
37
|
+
* 2-space JSON + trailing newline (matching the repo's other generated JSON).
|
|
38
|
+
*/
|
|
39
|
+
export function serializeScaffoldSidecar(entries) {
|
|
40
|
+
const files = [...entries].sort((a, b) => a.path.localeCompare(b.path));
|
|
41
|
+
return `${JSON.stringify({ version: SIDECAR_VERSION, files }, null, 2)}\n`;
|
|
42
|
+
}
|
|
43
|
+
/** Read the prior sidecar into a `path → hash` map. Missing or malformed sidecar → empty map. */
|
|
44
|
+
function readSidecar(repoRoot) {
|
|
45
|
+
const map = new Map();
|
|
46
|
+
const abs = sidecarPath(repoRoot);
|
|
47
|
+
if (!fs.existsSync(abs))
|
|
48
|
+
return map;
|
|
49
|
+
// Read errors (permissions, transient I/O) propagate — they are real operational problems, not a
|
|
50
|
+
// "malformed sidecar" we should silently swallow. Only a JSON parse failure is tolerated.
|
|
51
|
+
const raw = fs.readFileSync(abs, 'utf-8');
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// A malformed sidecar is treated as absent: every managed file becomes "untracked" and must
|
|
58
|
+
// either already match the current render (adopted) or be resolved via --force.
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(parsed.files)) {
|
|
62
|
+
for (const rawEntry of parsed.files) {
|
|
63
|
+
const entry = rawEntry;
|
|
64
|
+
if (typeof entry.path === 'string' && typeof entry.hash === 'string') {
|
|
65
|
+
map.set(entry.path, entry.hash);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decide what to do with one managed scaffold file. Pure — all filesystem state is passed in — so
|
|
73
|
+
* the decision table is unit-testable in isolation.
|
|
74
|
+
*
|
|
75
|
+
* | On-disk state | Result |
|
|
76
|
+
* |-------------------------------------------------|---------------|
|
|
77
|
+
* | missing | `recreated` |
|
|
78
|
+
* | matches the current render | `unchanged` |
|
|
79
|
+
* | differs from render, matches recorded hash | `updated` |
|
|
80
|
+
* | differs from render & recorded (or untracked) | `conflict` * |
|
|
81
|
+
* | …same, with `force` | `overwritten` |
|
|
82
|
+
*
|
|
83
|
+
* \* `unchanged` also adopts an untracked-but-in-sync file by recording its hash.
|
|
84
|
+
*/
|
|
85
|
+
export function decideManagedFile(input) {
|
|
86
|
+
const { render, current, recordedHash, force } = input;
|
|
87
|
+
const renderHash = hashScaffoldContent(render);
|
|
88
|
+
if (current === null)
|
|
89
|
+
return { status: 'recreated', write: true, recordHash: renderHash };
|
|
90
|
+
const currentHash = hashScaffoldContent(current);
|
|
91
|
+
if (currentHash === renderHash)
|
|
92
|
+
return { status: 'unchanged', write: false, recordHash: renderHash };
|
|
93
|
+
// Content differs from the current render.
|
|
94
|
+
if (recordedHash !== null && currentHash === recordedHash) {
|
|
95
|
+
// Pristine: the file is exactly what the toolkit last wrote, so it is safe to upgrade.
|
|
96
|
+
return { status: 'updated', write: true, recordHash: renderHash };
|
|
97
|
+
}
|
|
98
|
+
// Diverged from the recorded hash (user-edited) or untracked and not in sync.
|
|
99
|
+
if (force)
|
|
100
|
+
return { status: 'overwritten', write: true, recordHash: renderHash };
|
|
101
|
+
return { status: 'conflict', write: false, recordHash: null };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Seed `.aipm/scaffold.json` for a freshly scaffolded repo, recording the hash of every managed
|
|
105
|
+
* scaffold file as just written by `aipm init`. Called by {@link runInit} after the seed tree is
|
|
106
|
+
* written so the first `--refresh` has a baseline.
|
|
107
|
+
*/
|
|
108
|
+
export function writeScaffoldSidecar(repoRoot) {
|
|
109
|
+
const entries = buildManagedScaffoldFiles().map((file) => ({
|
|
110
|
+
path: file.path,
|
|
111
|
+
hash: hashScaffoldContent(file.content),
|
|
112
|
+
}));
|
|
113
|
+
const abs = sidecarPath(repoRoot);
|
|
114
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
115
|
+
fs.writeFileSync(abs, serializeScaffoldSidecar(entries), 'utf-8');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Refresh the toolkit-owned scaffold files of an existing repo at `repoRoot`, re-rendering each
|
|
119
|
+
* from the installed tooling and updating it in place when safe (see {@link decideManagedFile}).
|
|
120
|
+
* Writes the updated `.aipm/scaffold.json` sidecar and returns one {@link RefreshOutcome} per
|
|
121
|
+
* managed file. Never throws on conflict — conflicts are reported, not failures.
|
|
122
|
+
*/
|
|
123
|
+
export function runRefreshScaffold(repoRoot, opts = {}) {
|
|
124
|
+
const force = opts.force ?? false;
|
|
125
|
+
const resolved = path.resolve(repoRoot);
|
|
126
|
+
const recorded = readSidecar(resolved);
|
|
127
|
+
const outcomes = [];
|
|
128
|
+
const newEntries = [];
|
|
129
|
+
for (const file of buildManagedScaffoldFiles()) {
|
|
130
|
+
const abs = path.join(resolved, file.path);
|
|
131
|
+
const current = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf-8') : null;
|
|
132
|
+
const recordedHash = recorded.get(file.path) ?? null;
|
|
133
|
+
const decision = decideManagedFile({ render: file.content, current, recordedHash, force });
|
|
134
|
+
if (decision.write) {
|
|
135
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
136
|
+
fs.writeFileSync(abs, file.content, 'utf-8');
|
|
137
|
+
}
|
|
138
|
+
if (decision.recordHash !== null) {
|
|
139
|
+
newEntries.push({ path: file.path, hash: decision.recordHash });
|
|
140
|
+
}
|
|
141
|
+
else if (recordedHash !== null) {
|
|
142
|
+
// Conflict left untouched: preserve the prior recorded hash so it stays tracked.
|
|
143
|
+
newEntries.push({ path: file.path, hash: recordedHash });
|
|
144
|
+
}
|
|
145
|
+
outcomes.push({ path: file.path, status: decision.status });
|
|
146
|
+
}
|
|
147
|
+
const sidecarAbs = sidecarPath(resolved);
|
|
148
|
+
fs.mkdirSync(path.dirname(sidecarAbs), { recursive: true });
|
|
149
|
+
fs.writeFileSync(sidecarAbs, serializeScaffoldSidecar(newEntries), 'utf-8');
|
|
150
|
+
return outcomes;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=scaffold-refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold-refresh.js","sourceRoot":"","sources":["../../src/pipeline/scaffold-refresh.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline/types.d.ts
CHANGED
|
@@ -99,9 +99,15 @@ export interface ValidationResult {
|
|
|
99
99
|
* host software / the author). Generation refuses to overwrite it rather than clobber repo-root
|
|
100
100
|
* state.
|
|
101
101
|
*
|
|
102
|
+
* `default-marketplace-name` — the repo's effective marketplace `name` (or `owner.name`) is still
|
|
103
|
+
* a template placeholder (e.g. `ai-plugin-marketplace`, `my-ai-plugins`, `Your Name`). Two
|
|
104
|
+
* marketplaces registered under the same name collide on install — the later one shadows/strands
|
|
105
|
+
* the earlier one's plugins — so a placeholder must be renamed to a unique value. Always SOFT: a
|
|
106
|
+
* warning, never a build failure.
|
|
107
|
+
*
|
|
102
108
|
* @public
|
|
103
109
|
*/
|
|
104
|
-
export type FindingCode = 'envelope-invalid' | 'repo-config-invalid' | 'envelope-adherence' | 'schema-invalid' | 'name-consistency' | 'mcp-key-sync' | 'marketplace-registration' | 'freshness' | 'single-artifact-host' | 'root-artifact-collision';
|
|
110
|
+
export type FindingCode = 'envelope-invalid' | 'repo-config-invalid' | 'envelope-adherence' | 'schema-invalid' | 'name-consistency' | 'mcp-key-sync' | 'marketplace-registration' | 'freshness' | 'single-artifact-host' | 'root-artifact-collision' | 'default-marketplace-name';
|
|
105
111
|
/**
|
|
106
112
|
* A single validation finding.
|
|
107
113
|
*
|
|
@@ -139,6 +145,56 @@ export interface InitOptions {
|
|
|
139
145
|
* directory.
|
|
140
146
|
*/
|
|
141
147
|
name?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Marketplace name written into the generated repo-root registries' top-level `name` (and used
|
|
150
|
+
* for the registries' `owner.name`). This is the identity host platforms register the
|
|
151
|
+
* marketplace under, and it MUST be unique across marketplaces — two marketplaces sharing a name
|
|
152
|
+
* collide on install, with the later one shadowing/stranding the earlier one's plugins.
|
|
153
|
+
*
|
|
154
|
+
* Distinct from {@link InitOptions.name} (the `package.json` name). When omitted, `runInit`
|
|
155
|
+
* resolves a default at the I/O boundary (`${USER}-ai-plugins`, falling back to `my-ai-plugins`
|
|
156
|
+
* when `USER` is unset). `init-template.ts` is a pure function of its inputs, so the resolved
|
|
157
|
+
* value is passed in rather than read from the environment there.
|
|
158
|
+
*/
|
|
159
|
+
marketplaceName?: string;
|
|
160
|
+
/**
|
|
161
|
+
* Version of `@ai-plugin-marketplace/cli` to pin the generated `package.json`'s `cli`
|
|
162
|
+
* dev dependency to (as `^<cliVersion>`). The cli entrypoint supplies its own version here; cli
|
|
163
|
+
* and core ship independently and may differ (e.g. `cli 0.1.1` ships with `core 0.2.0`). When
|
|
164
|
+
* omitted, falls back to core's own version (the historical lockstep assumption).
|
|
165
|
+
*/
|
|
166
|
+
cliVersion?: string;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Options for {@link refreshScaffold}.
|
|
170
|
+
*
|
|
171
|
+
* @public
|
|
172
|
+
*/
|
|
173
|
+
export interface RefreshOptions {
|
|
174
|
+
/**
|
|
175
|
+
* Overwrite toolkit-owned scaffold files even when their on-disk content has diverged from what
|
|
176
|
+
* the toolkit last wrote (i.e. the user edited them). Without this, diverged files are reported
|
|
177
|
+
* as conflicts and left untouched.
|
|
178
|
+
*/
|
|
179
|
+
force?: boolean;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Per-file outcome of a {@link refreshScaffold} run. Returned (one per managed scaffold file) so
|
|
183
|
+
* the CLI can report what changed without re-deriving it.
|
|
184
|
+
*
|
|
185
|
+
* @public
|
|
186
|
+
*/
|
|
187
|
+
export interface RefreshOutcome {
|
|
188
|
+
/** Repo-relative POSIX path of the managed scaffold file. */
|
|
189
|
+
path: string;
|
|
190
|
+
/**
|
|
191
|
+
* - `updated` — content changed and was rewritten (file was pristine or `--force`).
|
|
192
|
+
* - `unchanged` — already matched the current render; nothing written.
|
|
193
|
+
* - `recreated` — file was missing and was written from the render.
|
|
194
|
+
* - `conflict` — on-disk content diverged from the recorded hash; left untouched (no `--force`).
|
|
195
|
+
* - `overwritten` — diverged but rewritten because `--force` was set.
|
|
196
|
+
*/
|
|
197
|
+
status: 'updated' | 'unchanged' | 'recreated' | 'conflict' | 'overwritten';
|
|
142
198
|
}
|
|
143
199
|
/**
|
|
144
200
|
* Options for {@link migrate}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEpF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,oEAOiB,CAAC;AAmBzC;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,EAAE,QAAQ,CAAC;CAClB;AAKD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEpF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,oEAOiB,CAAC;AAmBzC;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,EAAE,QAAQ,CAAC;CAClB;AAKD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,WAAW,GACnB,kBAAkB,GAClB,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,GAChB,kBAAkB,GAClB,cAAc,GACd,0BAA0B,GAC1B,WAAW,GACX,sBAAsB,GACtB,yBAAyB,GACzB,0BAA0B,CAAC;AAE/B;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAKD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC9B,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;CAC5E;AAKD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,MAAM,EAAE,sBAAsB,GAAG,SAAS,GAAG,QAAQ,CAAC;IACtD,mBAAmB;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAKD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,4DAA4D;IAC5D,gBAAgB,EAAE;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC5D,wFAAwF;IACxF,WAAW,EAAE;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CAC1D"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @see docs/specs/architecture.md §6, §8.1, §10.1, §10.2
|
|
9
9
|
*/
|
|
10
|
+
import type { AipmWorkspace } from '../config.js';
|
|
10
11
|
import type { Finding, TargetId, ValidateOptions, ValidationResult } from './types.js';
|
|
11
12
|
/**
|
|
12
13
|
* Minimum required artifacts for each target. Missing any of these when the target is
|
|
@@ -14,6 +15,17 @@ import type { Finding, TargetId, ValidateOptions, ValidationResult } from './typ
|
|
|
14
15
|
* Vercel requires at least one skills/<name>/SKILL.md — handled specially below.
|
|
15
16
|
*/
|
|
16
17
|
export declare const TARGET_MIN_REQUIRED: Record<TargetId, string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Repo-level check: warn (SOFT) when the effective marketplace `name` or `owner.name` is still a
|
|
20
|
+
* template placeholder. A placeholder name collides with the upstream `ai-plugin-marketplace`
|
|
21
|
+
* marketplace (or any other fork that kept the default) — when two marketplaces register under the
|
|
22
|
+
* same name, the later install shadows/strands the earlier's plugins. Emitting nothing when no
|
|
23
|
+
* marketplace metadata is declared (an empty `aipm init` repo before any name is set).
|
|
24
|
+
*
|
|
25
|
+
* Always SOFT: this is advice the author should act on, never a reason to fail the build (it does
|
|
26
|
+
* not flip `passed`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkDefaultMarketplaceName(repoRoot: string, workspace: AipmWorkspace | undefined): Finding[];
|
|
17
29
|
/**
|
|
18
30
|
* Validate an already-loaded `aipm.config.ts` value against AipmConfig's Zod schema.
|
|
19
31
|
* Loading from disk is the pipeline orchestrator's job (Stage 5). Here we only validate shape.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/pipeline/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/pipeline/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqCH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiCvF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAO1D,CAAC;AA8LF;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,aAAa,GAAG,SAAS,GACnC,OAAO,EAAE,CAuBX;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,EAAE,CAsBvF;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CA4FX;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CAmGX;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,EAAE,CAgD9F;AAoDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CA0FX;AAMD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CAOX;AAyZD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,eAAe,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,GACxC,OAAO,CAAC,gBAAgB,CAAC,CA+I3B"}
|
|
@@ -138,6 +138,98 @@ function hard(code, plugin, message, hint) {
|
|
|
138
138
|
return { severity: 'hard', code, plugin, message, ...(hint !== undefined ? { hint } : {}) };
|
|
139
139
|
}
|
|
140
140
|
// ---------------------------------------------------------------------------
|
|
141
|
+
// Default / placeholder marketplace name check
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
/**
|
|
144
|
+
* Marketplace `name` values that are template placeholders, not a real identity. `ai-plugin-marketplace`
|
|
145
|
+
* is the upstream template repo's own name (a fork that never renamed collides with upstream);
|
|
146
|
+
* `my-ai-plugins` is the placeholder the template ships and the fallback `aipm init` writes when
|
|
147
|
+
* `$USER` is unset (intentionally flagged so the author is nudged to pass `--name`).
|
|
148
|
+
*/
|
|
149
|
+
const PLACEHOLDER_MARKETPLACE_NAMES = new Set([
|
|
150
|
+
'ai-plugin-marketplace',
|
|
151
|
+
'my-ai-plugins',
|
|
152
|
+
]);
|
|
153
|
+
/**
|
|
154
|
+
* Marketplace `owner.name` values that are template placeholders rather than a real owner.
|
|
155
|
+
*/
|
|
156
|
+
const PLACEHOLDER_OWNER_NAMES = new Set([
|
|
157
|
+
'AI Plugin Marketplace Template',
|
|
158
|
+
'Your Name',
|
|
159
|
+
]);
|
|
160
|
+
/** Schema for the marketplace metadata this check reads from a generated registry JSON. */
|
|
161
|
+
const registryMetadataSchema = z
|
|
162
|
+
.object({
|
|
163
|
+
name: z.string().optional(),
|
|
164
|
+
owner: z.object({ name: z.string().optional() }).loose().optional(),
|
|
165
|
+
})
|
|
166
|
+
.loose();
|
|
167
|
+
/**
|
|
168
|
+
* Resolve the repo's effective marketplace identity for the default-name check. Prefers the
|
|
169
|
+
* authored `aipm.workspace.ts` (which `runValidate` already loaded) and falls back to reading a
|
|
170
|
+
* committed repo-root registry's top-level `name`/`owner.name` (the hand-authored / `aipm init`
|
|
171
|
+
* path, where no workspace exists). The first managed registry that parses and declares a `name`
|
|
172
|
+
* OR an `owner.name` wins; all generated registries carry the same marketplace identity, so any
|
|
173
|
+
* one is representative.
|
|
174
|
+
*/
|
|
175
|
+
function resolveMarketplaceIdentity(repoRoot, workspace) {
|
|
176
|
+
if (workspace !== undefined) {
|
|
177
|
+
const { marketplace } = workspace;
|
|
178
|
+
return {
|
|
179
|
+
name: marketplace.name,
|
|
180
|
+
...(marketplace.owner !== undefined ? { ownerName: marketplace.owner.name } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
for (const registryPath of managedRegistryPaths(repoRoot)) {
|
|
184
|
+
if (!fs.existsSync(registryPath))
|
|
185
|
+
continue;
|
|
186
|
+
const parsed = registryMetadataSchema.safeParse(tryReadJson(registryPath));
|
|
187
|
+
if (!parsed.success)
|
|
188
|
+
continue;
|
|
189
|
+
const { name, owner } = parsed.data;
|
|
190
|
+
// Only treat this registry as the identity source once it declares a `name` or an `owner.name`;
|
|
191
|
+
// an empty `{ "plugins": [] }` registry carries no identity, so keep scanning the others.
|
|
192
|
+
if (name === undefined && owner?.name === undefined)
|
|
193
|
+
continue;
|
|
194
|
+
return {
|
|
195
|
+
...(name !== undefined ? { name } : {}),
|
|
196
|
+
...(owner?.name !== undefined ? { ownerName: owner.name } : {}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Repo-level check: warn (SOFT) when the effective marketplace `name` or `owner.name` is still a
|
|
203
|
+
* template placeholder. A placeholder name collides with the upstream `ai-plugin-marketplace`
|
|
204
|
+
* marketplace (or any other fork that kept the default) — when two marketplaces register under the
|
|
205
|
+
* same name, the later install shadows/strands the earlier's plugins. Emitting nothing when no
|
|
206
|
+
* marketplace metadata is declared (an empty `aipm init` repo before any name is set).
|
|
207
|
+
*
|
|
208
|
+
* Always SOFT: this is advice the author should act on, never a reason to fail the build (it does
|
|
209
|
+
* not flip `passed`).
|
|
210
|
+
*/
|
|
211
|
+
export function checkDefaultMarketplaceName(repoRoot, workspace) {
|
|
212
|
+
const { name, ownerName } = resolveMarketplaceIdentity(repoRoot, workspace);
|
|
213
|
+
const findings = [];
|
|
214
|
+
if (name !== undefined && PLACEHOLDER_MARKETPLACE_NAMES.has(name)) {
|
|
215
|
+
findings.push({
|
|
216
|
+
severity: 'soft',
|
|
217
|
+
code: 'default-marketplace-name',
|
|
218
|
+
message: `Marketplace name '${name}' is a template default. Two marketplaces registered under the same name collide on install — the later one shadows/strands the earlier one's plugins (including the upstream 'ai-plugin-marketplace').`,
|
|
219
|
+
hint: "Rename it to a unique value (convention: '<your-handle>-ai-plugins'): set marketplace.name in aipm.workspace.ts, or the top-level `name` in the repo-root registries (.claude-plugin/.cursor-plugin/marketplace.json) when there is no workspace. New repos can pass `aipm init --name <name>`.",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (ownerName !== undefined && PLACEHOLDER_OWNER_NAMES.has(ownerName)) {
|
|
223
|
+
findings.push({
|
|
224
|
+
severity: 'soft',
|
|
225
|
+
code: 'default-marketplace-name',
|
|
226
|
+
message: `Marketplace owner name '${ownerName}' is a template default, not a real owner.`,
|
|
227
|
+
hint: 'Set marketplace.owner.name in aipm.workspace.ts (or the repo-root registries) to your name or organization.',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return findings;
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
141
233
|
// validateEnvelopeShape
|
|
142
234
|
// ---------------------------------------------------------------------------
|
|
143
235
|
/**
|
|
@@ -894,7 +986,17 @@ export async function runValidate(targetPath, opts) {
|
|
|
894
986
|
findings.push(...checkFreshness(pluginDir, distDir, envelope, ci));
|
|
895
987
|
}
|
|
896
988
|
}
|
|
897
|
-
// ── 6. Repo-level
|
|
989
|
+
// ── 6. Repo-level default/placeholder marketplace-name check (always SOFT) ──
|
|
990
|
+
// Marketplace identity is a REPO-level concern, so this runs only when validating a repo root
|
|
991
|
+
// (not a single plugin directory — that path stays scoped to the one plugin and short-circuits
|
|
992
|
+
// on envelope-invalid). Independent of registry generation: the effective name comes from
|
|
993
|
+
// `aipm.workspace.ts` when present, else from a committed repo-root registry's top-level
|
|
994
|
+
// `name`/`owner.name`. A placeholder name collides with the upstream marketplace and strands
|
|
995
|
+
// plugins, so warn — but never fail (soft, does not flip `passed`).
|
|
996
|
+
if (path.resolve(targetPath) === repoRoot) {
|
|
997
|
+
findings.push(...checkDefaultMarketplaceName(repoRoot, workspace));
|
|
998
|
+
}
|
|
999
|
+
// ── 7. Repo-level generation checks (only when generation is opted in) ──────
|
|
898
1000
|
// Registries AND single-artifact-host root artifacts are repo-scoped, so these run once per repo
|
|
899
1001
|
// against EVERY plugin under the repo root — not just the plugins in `pluginDirs` (length-1 for
|
|
900
1002
|
// single-plugin input). That mirrors how `runBuild` regenerates the repo-complete output, so a
|