@shrkcrft/templates 0.1.0-alpha.30 → 0.1.0-alpha.31
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/template-definition.d.ts +23 -1
- package/dist/template-definition.d.ts.map +1 -1
- package/dist/template-definition.js +4 -0
- package/dist/template-loader.d.ts +17 -1
- package/dist/template-loader.d.ts.map +1 -1
- package/dist/template-loader.js +98 -13
- package/dist/template-remainder.d.ts +71 -0
- package/dist/template-remainder.d.ts.map +1 -0
- package/dist/template-remainder.js +63 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ITemplateVariable, TemplateVariableValues } from './template-variable.js';
|
|
2
|
+
import type { TemplateRemainder } from './template-remainder.js';
|
|
2
3
|
/**
|
|
3
4
|
* Subset of @shrkcrft/generator's `IPlannedChange` re-declared here to
|
|
4
5
|
* avoid a generator → templates → generator cycle. The two shapes are kept
|
|
@@ -144,6 +145,22 @@ export interface ITemplateDefinition {
|
|
|
144
145
|
changes?: ChangesResolver;
|
|
145
146
|
/** Post-generation notes shown to the user. */
|
|
146
147
|
postGenerationNotes?: readonly string[];
|
|
148
|
+
/**
|
|
149
|
+
* Work this template deliberately does NOT perform (build config, a path
|
|
150
|
+
* alias, workspace registration, …). Machine-readable, so a generator whose
|
|
151
|
+
* output needs a follow-up reads as a documented remainder, not a bug.
|
|
152
|
+
* Printed after generation; checked by template lint.
|
|
153
|
+
*/
|
|
154
|
+
notScaffolded?: readonly (TemplateRemainder | `${TemplateRemainder}`)[];
|
|
155
|
+
/**
|
|
156
|
+
* Manual steps the human performs after generation. `covers` names the
|
|
157
|
+
* remainder(s) a step handles, so lint can tell a covered remainder from an
|
|
158
|
+
* undeclared one.
|
|
159
|
+
*/
|
|
160
|
+
manualSteps?: readonly {
|
|
161
|
+
readonly description: string;
|
|
162
|
+
readonly covers?: readonly (TemplateRemainder | `${TemplateRemainder}`)[];
|
|
163
|
+
}[];
|
|
147
164
|
/** Related knowledge entry IDs. */
|
|
148
165
|
related?: readonly string[];
|
|
149
166
|
/**
|
|
@@ -155,7 +172,12 @@ export interface ITemplateDefinition {
|
|
|
155
172
|
metadata?: {
|
|
156
173
|
/** Files that must NOT be produced by this template (regex fragments). */
|
|
157
174
|
forbiddenPathFragments?: readonly string[];
|
|
158
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* WorkspaceProfile ids this template depends on (`has-typescript`,
|
|
177
|
+
* `is-library`, … — `shrk profiles list --kind workspace`). Resolved by the
|
|
178
|
+
* self-config doctor (`template-profile-missing`); not evaluated as a
|
|
179
|
+
* filter by the renderer.
|
|
180
|
+
*/
|
|
159
181
|
requiredProfileIds?: readonly string[];
|
|
160
182
|
/** Convention ids this template's outputs are expected to satisfy. */
|
|
161
183
|
requiredConventionIds?: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-definition.d.ts","sourceRoot":"","sources":["../src/template-definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"template-definition.d.ts","sourceRoot":"","sources":["../src/template-definition.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,10 @@ export function defineTemplate(input) {
|
|
|
15
15
|
postGenerationNotes: input.postGenerationNotes
|
|
16
16
|
? Object.freeze([...input.postGenerationNotes])
|
|
17
17
|
: undefined,
|
|
18
|
+
notScaffolded: input.notScaffolded ? Object.freeze([...input.notScaffolded]) : undefined,
|
|
19
|
+
manualSteps: input.manualSteps
|
|
20
|
+
? Object.freeze(input.manualSteps.map((s) => Object.freeze({ ...s, ...(s.covers ? { covers: Object.freeze([...s.covers]) } : {}) })))
|
|
21
|
+
: undefined,
|
|
18
22
|
related: input.related ? Object.freeze([...input.related]) : undefined,
|
|
19
23
|
};
|
|
20
24
|
}
|
|
@@ -1,9 +1,25 @@
|
|
|
1
|
-
import { type IImportContext } from '@shrkcrft/core';
|
|
1
|
+
import { type IImportContext, type IRejectedEntry } from '@shrkcrft/core';
|
|
2
2
|
import type { ITemplateDefinition } from './template-definition.js';
|
|
3
|
+
/**
|
|
4
|
+
* THE template-shape predicate the loader registers by. Exported so the
|
|
5
|
+
* unregistered-export check asks exactly the question the loader asks.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isTemplate(value: unknown): value is ITemplateDefinition;
|
|
8
|
+
/**
|
|
9
|
+
* Why `value` is not a template: one `<field>: <message>` per field
|
|
10
|
+
* {@link isTemplate} requires and `value` lacks — `[]` when it is one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function templateRejectionReasons(value: unknown): readonly string[];
|
|
3
13
|
export interface ILoadedTemplates {
|
|
4
14
|
templates: ITemplateDefinition[];
|
|
5
15
|
warnings: string[];
|
|
6
16
|
sourceFiles: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Every template the file declared that the loader refused (round 12,
|
|
19
|
+
* 12.1): a list member (or the `default` object) meant as a template that
|
|
20
|
+
* fails {@link isTemplate}, and a different object reusing an id.
|
|
21
|
+
*/
|
|
22
|
+
rejected: IRejectedEntry[];
|
|
7
23
|
}
|
|
8
24
|
export interface ILoadTemplatesOptions {
|
|
9
25
|
importContext?: IImportContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-loader.d.ts","sourceRoot":"","sources":["../src/template-loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"template-loader.d.ts","sourceRoot":"","sources":["../src/template-loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAA8B,MAAM,gBAAgB,CAAC;AACtG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAIvE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAO1E;AAmCD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;OAIG;IACH,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,CAAC,EAAE,cAAc,CAAC;CAChC;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAiF3B"}
|
package/dist/template-loader.js
CHANGED
|
@@ -1,18 +1,71 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { safeImport } from '@shrkcrft/core';
|
|
3
|
-
|
|
2
|
+
import { RejectionCause, safeImport } from '@shrkcrft/core';
|
|
3
|
+
/**
|
|
4
|
+
* THE template-shape predicate the loader registers by. Exported so the
|
|
5
|
+
* unregistered-export check asks exactly the question the loader asks.
|
|
6
|
+
*/
|
|
7
|
+
export function isTemplate(value) {
|
|
4
8
|
if (!value || typeof value !== 'object')
|
|
5
9
|
return false;
|
|
6
10
|
const v = value;
|
|
7
11
|
return typeof v.id === 'string' && typeof v.name === 'string';
|
|
8
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Why `value` is not a template: one `<field>: <message>` per field
|
|
15
|
+
* {@link isTemplate} requires and `value` lacks — `[]` when it is one.
|
|
16
|
+
*/
|
|
17
|
+
export function templateRejectionReasons(value) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
return ['(entry): must be an object'];
|
|
20
|
+
const v = value;
|
|
21
|
+
const out = [];
|
|
22
|
+
if (typeof v.id !== 'string')
|
|
23
|
+
out.push('id: must be a string');
|
|
24
|
+
if (typeof v.name !== 'string')
|
|
25
|
+
out.push('name: must be a string');
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
/** An object plainly meant as a template: it carries a string `id`, `name` or `title`. */
|
|
29
|
+
function isTemplateCandidate(value) {
|
|
30
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
const v = value;
|
|
33
|
+
return typeof v.id === 'string' || typeof v.name === 'string' || typeof v.title === 'string';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The list fields every consumer reads as arrays (`t.tags.length` in
|
|
37
|
+
* `templates list`). `ITemplateDefinition` declares them, but THE predicate
|
|
38
|
+
* accepts a template by id + name only — so, exactly as the knowledge loader
|
|
39
|
+
* does for its list fields, a missing one is normalised to a frozen `[]`
|
|
40
|
+
* rather than reaching a consumer as `undefined` (round 12, 12.1d: `templates
|
|
41
|
+
* list` crashed on a pack template with no `tags`).
|
|
42
|
+
*/
|
|
43
|
+
const TEMPLATE_LIST_FIELDS = ['tags', 'scope', 'appliesWhen', 'variables'];
|
|
44
|
+
function normalizeTemplateListFields(t, file, warnings) {
|
|
45
|
+
const rec = t;
|
|
46
|
+
for (const field of TEMPLATE_LIST_FIELDS) {
|
|
47
|
+
const value = rec[field];
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
continue;
|
|
50
|
+
if (value !== undefined && value !== null) {
|
|
51
|
+
warnings.push(`${file}: template "${t.id}" has a non-list \`${field}\` (${typeof value}) — treated as []`);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
rec[field] = Object.freeze([]);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
warnings.push(`${file}: template "${t.id}" is frozen without a \`${field}\` list — read as []`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
9
61
|
export async function loadTemplatesFromFile(filePath, options = {}) {
|
|
10
62
|
const warnings = [];
|
|
11
63
|
const templates = [];
|
|
12
64
|
const sourceFiles = [];
|
|
65
|
+
const rejected = [];
|
|
13
66
|
if (!existsSync(filePath)) {
|
|
14
67
|
warnings.push(`Template file not found: ${filePath}`);
|
|
15
|
-
return { templates, warnings, sourceFiles };
|
|
68
|
+
return { templates, warnings, sourceFiles, rejected };
|
|
16
69
|
}
|
|
17
70
|
sourceFiles.push(filePath);
|
|
18
71
|
const result = options.importContext
|
|
@@ -21,28 +74,60 @@ export async function loadTemplatesFromFile(filePath, options = {}) {
|
|
|
21
74
|
if (!result.ok) {
|
|
22
75
|
const label = result.timedOut ? 'timed out importing' : 'Failed to import';
|
|
23
76
|
warnings.push(`${label} ${filePath}: ${result.error.message}`);
|
|
24
|
-
return { templates, warnings, sourceFiles };
|
|
77
|
+
return { templates, warnings, sourceFiles, rejected };
|
|
25
78
|
}
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
79
|
+
// First OBJECT per id, and the export it came from. The same object exported
|
|
80
|
+
// twice (`export const t` + `export default [t]`) is one template; a
|
|
81
|
+
// DIFFERENT object reusing an id is silently shadowed — so say so.
|
|
82
|
+
const firstById = new Map();
|
|
83
|
+
const tryPush = (v, key, at, candidate) => {
|
|
84
|
+
if (!isTemplate(v)) {
|
|
85
|
+
if (candidate && isTemplateCandidate(v)) {
|
|
86
|
+
const id = v.id;
|
|
87
|
+
rejected.push({
|
|
88
|
+
file: filePath,
|
|
89
|
+
index: at.index,
|
|
90
|
+
exportName: at.exportName,
|
|
91
|
+
...(typeof id === 'string' ? { entryId: id } : {}),
|
|
92
|
+
reasons: templateRejectionReasons(v),
|
|
93
|
+
cause: RejectionCause.Invalid,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
29
96
|
return;
|
|
30
|
-
|
|
97
|
+
}
|
|
98
|
+
const first = firstById.get(v.id);
|
|
99
|
+
if (first) {
|
|
100
|
+
if (first.value !== v) {
|
|
101
|
+
warnings.push(`duplicate id "${v.id}" in ${filePath} (export "${key}") shadowed by an earlier export "${first.key}" — only the first is registered`);
|
|
102
|
+
rejected.push({
|
|
103
|
+
file: filePath,
|
|
104
|
+
index: at.index,
|
|
105
|
+
exportName: at.exportName,
|
|
106
|
+
entryId: v.id,
|
|
107
|
+
reasons: [`id: "${v.id}" is already declared by export "${first.key}" — only the first is registered`],
|
|
108
|
+
cause: RejectionCause.DuplicateId,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
31
111
|
return;
|
|
32
|
-
|
|
112
|
+
}
|
|
113
|
+
firstById.set(v.id, { value: v, key });
|
|
114
|
+
normalizeTemplateListFields(v, filePath, warnings);
|
|
33
115
|
templates.push(v);
|
|
34
116
|
};
|
|
35
117
|
for (const key of Object.keys(result.module)) {
|
|
36
118
|
const v = result.module[key];
|
|
37
119
|
if (isTemplate(v)) {
|
|
38
|
-
tryPush(v);
|
|
120
|
+
tryPush(v, key, { exportName: key, index: -1 }, key === 'default');
|
|
39
121
|
}
|
|
40
122
|
else if (Array.isArray(v)) {
|
|
41
|
-
|
|
42
|
-
|
|
123
|
+
// `default[3]`: the index tells two members of one array export apart.
|
|
124
|
+
v.forEach((item, i) => tryPush(item, `${key}[${i}]`, { exportName: key, index: i }, true));
|
|
125
|
+
}
|
|
126
|
+
else if (key === 'default') {
|
|
127
|
+
tryPush(v, key, { exportName: 'default', index: -1 }, true);
|
|
43
128
|
}
|
|
44
129
|
}
|
|
45
130
|
if (templates.length === 0)
|
|
46
131
|
warnings.push(`No templates exported by ${filePath}`);
|
|
47
|
-
return { templates, warnings, sourceFiles };
|
|
132
|
+
return { templates, warnings, sourceFiles, rejected };
|
|
48
133
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed vocabulary of work a template deliberately does NOT perform.
|
|
3
|
+
*
|
|
4
|
+
* Two sibling templates used to differ on whether they emitted build config or
|
|
5
|
+
* patched the workspace path map, and nothing machine-readable said so — a
|
|
6
|
+
* generator whose output did not build read as a generator bug instead of a
|
|
7
|
+
* documented remainder. `ITemplateDefinition.notScaffolded` / `manualSteps`
|
|
8
|
+
* name the remainder with these values; template lint checks the shape, and
|
|
9
|
+
* `gen` / `templates preview` / `templates get` print it.
|
|
10
|
+
*/
|
|
11
|
+
export declare enum TemplateRemainder {
|
|
12
|
+
BuildConfig = "build-config",
|
|
13
|
+
PathAlias = "path-alias",
|
|
14
|
+
WorkspaceRegistration = "workspace-registration",
|
|
15
|
+
Tests = "tests",
|
|
16
|
+
Docs = "docs",
|
|
17
|
+
Other = "other"
|
|
18
|
+
}
|
|
19
|
+
/** Every remainder value, for validation and error messages. */
|
|
20
|
+
export declare const TEMPLATE_REMAINDERS: readonly string[];
|
|
21
|
+
export declare function isTemplateRemainder(value: unknown): value is TemplateRemainder;
|
|
22
|
+
/**
|
|
23
|
+
* The remainder as structured fields — the ONE machine shape: `templates get
|
|
24
|
+
* --json` and MCP `get_template` both spread it (round 11 review R11-GAP-7:
|
|
25
|
+
* MCP carried neither field, so an agent never learned what a template leaves
|
|
26
|
+
* undone). Always present, `[]` when undeclared.
|
|
27
|
+
*/
|
|
28
|
+
export declare function templateRemainderFields(template: {
|
|
29
|
+
readonly notScaffolded?: readonly string[];
|
|
30
|
+
readonly manualSteps?: readonly {
|
|
31
|
+
readonly description: string;
|
|
32
|
+
readonly covers?: readonly string[];
|
|
33
|
+
}[];
|
|
34
|
+
}): {
|
|
35
|
+
readonly notScaffolded: readonly string[];
|
|
36
|
+
readonly manualSteps: readonly {
|
|
37
|
+
readonly description: string;
|
|
38
|
+
readonly covers?: readonly string[];
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* {@link templateRemainderFields} with the empty fields left out — the compact
|
|
43
|
+
* form a task packet's template rows carry (`task --json`, MCP
|
|
44
|
+
* `get_task_packet`), so an undeclared remainder adds no bytes.
|
|
45
|
+
*/
|
|
46
|
+
export declare function templateRemainderSummary(template: {
|
|
47
|
+
readonly notScaffolded?: readonly string[];
|
|
48
|
+
readonly manualSteps?: readonly {
|
|
49
|
+
readonly description: string;
|
|
50
|
+
readonly covers?: readonly string[];
|
|
51
|
+
}[];
|
|
52
|
+
}): {
|
|
53
|
+
readonly notScaffolded?: readonly string[];
|
|
54
|
+
readonly manualSteps?: readonly {
|
|
55
|
+
readonly description: string;
|
|
56
|
+
readonly covers?: readonly string[];
|
|
57
|
+
}[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The printable remainder block for a template — the ONE wording every surface
|
|
61
|
+
* (gen, templates preview/get) prints after `postGenerationNotes`. Empty when
|
|
62
|
+
* the template declares neither field.
|
|
63
|
+
*/
|
|
64
|
+
export declare function templateRemainderLines(template: {
|
|
65
|
+
readonly notScaffolded?: readonly string[];
|
|
66
|
+
readonly manualSteps?: readonly {
|
|
67
|
+
readonly description: string;
|
|
68
|
+
readonly covers?: readonly string[];
|
|
69
|
+
}[];
|
|
70
|
+
}): readonly string[];
|
|
71
|
+
//# sourceMappingURL=template-remainder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-remainder.d.ts","sourceRoot":"","sources":["../src/template-remainder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,oBAAY,iBAAiB;IAC3B,WAAW,iBAAiB;IAC5B,SAAS,eAAe;IACxB,qBAAqB,2BAA2B;IAChD,KAAK,UAAU;IACf,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,gEAAgE;AAChE,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAoD,CAAC;AAEtG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAE9E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACzG,GAAG;IACF,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,SAAS;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACxG,CAEA;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE;IACjD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACzG,GAAG;IACF,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACzG,CAKA;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACzG,GAAG,SAAS,MAAM,EAAE,CAapB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed vocabulary of work a template deliberately does NOT perform.
|
|
3
|
+
*
|
|
4
|
+
* Two sibling templates used to differ on whether they emitted build config or
|
|
5
|
+
* patched the workspace path map, and nothing machine-readable said so — a
|
|
6
|
+
* generator whose output did not build read as a generator bug instead of a
|
|
7
|
+
* documented remainder. `ITemplateDefinition.notScaffolded` / `manualSteps`
|
|
8
|
+
* name the remainder with these values; template lint checks the shape, and
|
|
9
|
+
* `gen` / `templates preview` / `templates get` print it.
|
|
10
|
+
*/
|
|
11
|
+
export var TemplateRemainder;
|
|
12
|
+
(function (TemplateRemainder) {
|
|
13
|
+
TemplateRemainder["BuildConfig"] = "build-config";
|
|
14
|
+
TemplateRemainder["PathAlias"] = "path-alias";
|
|
15
|
+
TemplateRemainder["WorkspaceRegistration"] = "workspace-registration";
|
|
16
|
+
TemplateRemainder["Tests"] = "tests";
|
|
17
|
+
TemplateRemainder["Docs"] = "docs";
|
|
18
|
+
TemplateRemainder["Other"] = "other";
|
|
19
|
+
})(TemplateRemainder || (TemplateRemainder = {}));
|
|
20
|
+
/** Every remainder value, for validation and error messages. */
|
|
21
|
+
export const TEMPLATE_REMAINDERS = Object.freeze(Object.values(TemplateRemainder));
|
|
22
|
+
export function isTemplateRemainder(value) {
|
|
23
|
+
return typeof value === 'string' && TEMPLATE_REMAINDERS.includes(value);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The remainder as structured fields — the ONE machine shape: `templates get
|
|
27
|
+
* --json` and MCP `get_template` both spread it (round 11 review R11-GAP-7:
|
|
28
|
+
* MCP carried neither field, so an agent never learned what a template leaves
|
|
29
|
+
* undone). Always present, `[]` when undeclared.
|
|
30
|
+
*/
|
|
31
|
+
export function templateRemainderFields(template) {
|
|
32
|
+
return { notScaffolded: template.notScaffolded ?? [], manualSteps: template.manualSteps ?? [] };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* {@link templateRemainderFields} with the empty fields left out — the compact
|
|
36
|
+
* form a task packet's template rows carry (`task --json`, MCP
|
|
37
|
+
* `get_task_packet`), so an undeclared remainder adds no bytes.
|
|
38
|
+
*/
|
|
39
|
+
export function templateRemainderSummary(template) {
|
|
40
|
+
return {
|
|
41
|
+
...(template.notScaffolded && template.notScaffolded.length > 0 ? { notScaffolded: template.notScaffolded } : {}),
|
|
42
|
+
...(template.manualSteps && template.manualSteps.length > 0 ? { manualSteps: template.manualSteps } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The printable remainder block for a template — the ONE wording every surface
|
|
47
|
+
* (gen, templates preview/get) prints after `postGenerationNotes`. Empty when
|
|
48
|
+
* the template declares neither field.
|
|
49
|
+
*/
|
|
50
|
+
export function templateRemainderLines(template) {
|
|
51
|
+
const lines = [];
|
|
52
|
+
if (template.notScaffolded && template.notScaffolded.length > 0) {
|
|
53
|
+
lines.push(`Not scaffolded by this template: ${template.notScaffolded.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
if (template.manualSteps && template.manualSteps.length > 0) {
|
|
56
|
+
lines.push('Manual steps:');
|
|
57
|
+
for (const step of template.manualSteps) {
|
|
58
|
+
const covers = step.covers && step.covers.length > 0 ? ` (covers: ${step.covers.join(', ')})` : '';
|
|
59
|
+
lines.push(` • ${step.description}${covers}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return lines;
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/templates",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.31",
|
|
4
4
|
"description": "SharkCraft templates: typed template definitions, registry, variable validation, rendering.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
46
|
+
"@shrkcrft/core": "^0.1.0-alpha.31"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|