@tanstack/cta-engine 0.47.0 → 0.49.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/CHANGELOG.md +12 -0
- package/dist/attribution.js +162 -0
- package/dist/index.js +2 -1
- package/dist/template-file.js +6 -1
- package/dist/types/attribution.d.ts +12 -0
- package/dist/types/custom-add-ons/add-on.d.ts +4 -8
- package/dist/types/custom-add-ons/shared.d.ts +1 -8
- package/dist/types/environment.d.ts +9 -8
- package/dist/types/index.d.ts +5 -2
- package/dist/types/types.d.ts +72 -0
- package/dist/types.js +20 -0
- package/package.json +1 -1
- package/src/attribution.ts +245 -0
- package/src/environment.ts +13 -9
- package/src/index.ts +12 -1
- package/src/template-file.ts +14 -3
- package/src/types.ts +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tanstack/cta-engine
|
|
2
2
|
|
|
3
|
+
## 0.49.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added categories, colors, exclusive tagging, addon file attribution utils, and railway addon ([#303](https://github.com/TanStack/create-tsrouter-app/pull/303))
|
|
8
|
+
|
|
9
|
+
## 0.48.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- no will prompt about overriding a directory that has contents ([#289](https://github.com/TanStack/create-tsrouter-app/pull/289))
|
|
14
|
+
|
|
3
15
|
## 0.47.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
function normalizePath(path) {
|
|
2
|
+
let p = path.startsWith('./') ? path.slice(2) : path;
|
|
3
|
+
p = p.replace(/\.ejs$/, '').replace(/_dot_/g, '.');
|
|
4
|
+
const match = p.match(/^(.+\/)?__([^_]+)__(.+)$/);
|
|
5
|
+
return match ? (match[1] || '') + match[3] : p;
|
|
6
|
+
}
|
|
7
|
+
async function getFileProvenance(filePath, framework, addOns, starter) {
|
|
8
|
+
const target = filePath.startsWith('./') ? filePath.slice(2) : filePath;
|
|
9
|
+
if (starter) {
|
|
10
|
+
const files = await starter.getFiles();
|
|
11
|
+
if (files.some((f) => normalizePath(f) === target)) {
|
|
12
|
+
return {
|
|
13
|
+
source: 'starter',
|
|
14
|
+
sourceId: starter.id,
|
|
15
|
+
sourceName: starter.name,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Order add-ons by type then phase (matches writeFiles order), check in reverse
|
|
20
|
+
const typeOrder = ['add-on', 'example', 'toolchain', 'deployment'];
|
|
21
|
+
const phaseOrder = ['setup', 'add-on', 'example'];
|
|
22
|
+
const ordered = typeOrder.flatMap((type) => phaseOrder.flatMap((phase) => addOns.filter((a) => a.phase === phase && a.type === type)));
|
|
23
|
+
for (let i = ordered.length - 1; i >= 0; i--) {
|
|
24
|
+
const files = await ordered[i].getFiles();
|
|
25
|
+
if (files.some((f) => normalizePath(f) === target)) {
|
|
26
|
+
return {
|
|
27
|
+
source: 'add-on',
|
|
28
|
+
sourceId: ordered[i].id,
|
|
29
|
+
sourceName: ordered[i].name,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const frameworkFiles = await framework.getFiles();
|
|
34
|
+
if (frameworkFiles.some((f) => normalizePath(f) === target)) {
|
|
35
|
+
return {
|
|
36
|
+
source: 'framework',
|
|
37
|
+
sourceId: framework.id,
|
|
38
|
+
sourceName: framework.name,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Build injection patterns from integrations (for source files)
|
|
44
|
+
function integrationInjections(int) {
|
|
45
|
+
const source = { sourceId: int._sourceId, sourceName: int._sourceName };
|
|
46
|
+
const injections = [];
|
|
47
|
+
const appliesTo = (path) => {
|
|
48
|
+
if (int.type === 'vite-plugin')
|
|
49
|
+
return path.includes('vite.config');
|
|
50
|
+
if (int.type === 'provider' ||
|
|
51
|
+
int.type === 'root-provider' ||
|
|
52
|
+
int.type === 'devtools') {
|
|
53
|
+
return path.includes('__root') || path.includes('root.tsx');
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
};
|
|
57
|
+
if (int.import) {
|
|
58
|
+
const prefix = int.import.split(' from ')[0];
|
|
59
|
+
injections.push({
|
|
60
|
+
matches: (line) => line.includes(prefix),
|
|
61
|
+
appliesTo,
|
|
62
|
+
source,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const code = int.code || int.jsName;
|
|
66
|
+
if (code) {
|
|
67
|
+
injections.push({
|
|
68
|
+
matches: (line) => line.includes(code),
|
|
69
|
+
appliesTo,
|
|
70
|
+
source,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return injections;
|
|
74
|
+
}
|
|
75
|
+
// Build injection pattern from a dependency (for package.json)
|
|
76
|
+
function dependencyInjection(dep) {
|
|
77
|
+
return {
|
|
78
|
+
matches: (line) => line.includes(`"${dep.name}"`),
|
|
79
|
+
appliesTo: (path) => path.endsWith('package.json'),
|
|
80
|
+
source: { sourceId: dep.sourceId, sourceName: dep.sourceName },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function computeAttribution(input) {
|
|
84
|
+
const { framework, chosenAddOns, starter, files } = input;
|
|
85
|
+
// Collect integrations tagged with source
|
|
86
|
+
const integrations = chosenAddOns.flatMap((addOn) => (addOn.integrations || []).map((int) => ({
|
|
87
|
+
...int,
|
|
88
|
+
_sourceId: addOn.id,
|
|
89
|
+
_sourceName: addOn.name,
|
|
90
|
+
})));
|
|
91
|
+
// Collect dependencies from add-ons (from packageAdditions or packageTemplate)
|
|
92
|
+
const dependencies = chosenAddOns.flatMap((addOn) => {
|
|
93
|
+
const result = [];
|
|
94
|
+
const source = { sourceId: addOn.id, sourceName: addOn.name };
|
|
95
|
+
const addDeps = (deps, type) => {
|
|
96
|
+
if (!deps)
|
|
97
|
+
return;
|
|
98
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
99
|
+
if (typeof version === 'string') {
|
|
100
|
+
result.push({ name, version, type, ...source });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
// From static package.json
|
|
105
|
+
addDeps(addOn.packageAdditions?.dependencies, 'dependency');
|
|
106
|
+
addDeps(addOn.packageAdditions?.devDependencies, 'devDependency');
|
|
107
|
+
// From package.json.ejs template (strip EJS tags and parse)
|
|
108
|
+
if (addOn.packageTemplate) {
|
|
109
|
+
try {
|
|
110
|
+
const tmpl = JSON.parse(addOn.packageTemplate.replace(/"[^"]*<%[^%]*%>[^"]*"/g, '""'));
|
|
111
|
+
addDeps(tmpl.dependencies, 'dependency');
|
|
112
|
+
addDeps(tmpl.devDependencies, 'devDependency');
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
});
|
|
118
|
+
// Build unified injection patterns from both integrations and dependencies
|
|
119
|
+
const injections = [
|
|
120
|
+
...integrations.flatMap(integrationInjections),
|
|
121
|
+
...dependencies.map(dependencyInjection),
|
|
122
|
+
];
|
|
123
|
+
const attributedFiles = {};
|
|
124
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
125
|
+
const provenance = await getFileProvenance(filePath, framework, chosenAddOns, starter);
|
|
126
|
+
if (!provenance)
|
|
127
|
+
continue;
|
|
128
|
+
const lines = content.split('\n');
|
|
129
|
+
const relevant = injections.filter((inj) => inj.appliesTo(filePath));
|
|
130
|
+
// Find injected lines
|
|
131
|
+
const injectedLines = new Map();
|
|
132
|
+
for (const inj of relevant) {
|
|
133
|
+
lines.forEach((line, i) => {
|
|
134
|
+
if (inj.matches(line) && !injectedLines.has(i + 1)) {
|
|
135
|
+
injectedLines.set(i + 1, inj.source);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
attributedFiles[filePath] = {
|
|
140
|
+
content,
|
|
141
|
+
provenance,
|
|
142
|
+
lineAttributions: lines.map((_, i) => {
|
|
143
|
+
const lineNum = i + 1;
|
|
144
|
+
const inj = injectedLines.get(lineNum);
|
|
145
|
+
return inj
|
|
146
|
+
? {
|
|
147
|
+
line: lineNum,
|
|
148
|
+
sourceId: inj.sourceId,
|
|
149
|
+
sourceName: inj.sourceName,
|
|
150
|
+
type: 'injected',
|
|
151
|
+
}
|
|
152
|
+
: {
|
|
153
|
+
line: lineNum,
|
|
154
|
+
sourceId: provenance.sourceId,
|
|
155
|
+
sourceName: provenance.sourceName,
|
|
156
|
+
type: 'original',
|
|
157
|
+
};
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return { attributedFiles, dependencies };
|
|
162
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createApp } from './create-app.js';
|
|
2
|
+
export { computeAttribution } from './attribution.js';
|
|
2
3
|
export { addToApp } from './add-to-app.js';
|
|
3
|
-
export { finalizeAddOns, getAllAddOns, populateAddOnOptionsDefaults } from './add-ons.js';
|
|
4
|
+
export { finalizeAddOns, getAllAddOns, populateAddOnOptionsDefaults, } from './add-ons.js';
|
|
4
5
|
export { loadRemoteAddOn } from './custom-add-ons/add-on.js';
|
|
5
6
|
export { loadStarter } from './custom-add-ons/starter.js';
|
|
6
7
|
export { createMemoryEnvironment, createDefaultEnvironment, } from './environment.js';
|
package/dist/template-file.js
CHANGED
|
@@ -26,11 +26,16 @@ export function createTemplateFile(environment, options) {
|
|
|
26
26
|
this.name = 'IgnoreFileError';
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
// Collect integrations and tag them with source add-on for attribution
|
|
29
30
|
const integrations = [];
|
|
30
31
|
for (const addOn of options.chosenAddOns) {
|
|
31
32
|
if (addOn.integrations) {
|
|
32
33
|
for (const integration of addOn.integrations) {
|
|
33
|
-
integrations.push(
|
|
34
|
+
integrations.push({
|
|
35
|
+
...integration,
|
|
36
|
+
_sourceId: addOn.id,
|
|
37
|
+
_sourceName: addOn.name,
|
|
38
|
+
});
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AddOn, AttributedFile, DependencyAttribution, Framework, Starter } from './types.js';
|
|
2
|
+
export interface AttributionInput {
|
|
3
|
+
framework: Framework;
|
|
4
|
+
chosenAddOns: Array<AddOn>;
|
|
5
|
+
starter?: Starter;
|
|
6
|
+
files: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface AttributionOutput {
|
|
9
|
+
attributedFiles: Record<string, AttributedFile>;
|
|
10
|
+
dependencies: Array<DependencyAttribution>;
|
|
11
|
+
}
|
|
12
|
+
export declare function computeAttribution(input: AttributionInput): Promise<AttributionOutput>;
|
|
@@ -39,6 +39,9 @@ export declare function generateProject(persistedOptions: PersistedOptions): Pro
|
|
|
39
39
|
link?: string | undefined;
|
|
40
40
|
license?: string | undefined;
|
|
41
41
|
warning?: string | undefined;
|
|
42
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
43
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
44
|
+
color?: string | undefined;
|
|
42
45
|
priority?: number | undefined;
|
|
43
46
|
routes?: {
|
|
44
47
|
path: string;
|
|
@@ -67,14 +70,7 @@ export declare function generateProject(persistedOptions: PersistedOptions): Pro
|
|
|
67
70
|
}[] | undefined;
|
|
68
71
|
readme?: string | undefined;
|
|
69
72
|
};
|
|
70
|
-
output:
|
|
71
|
-
files: Record<string, string>;
|
|
72
|
-
deletedFiles: Array<string>;
|
|
73
|
-
commands: Array<{
|
|
74
|
-
command: string;
|
|
75
|
-
args: Array<string>;
|
|
76
|
-
}>;
|
|
77
|
-
};
|
|
73
|
+
output: import("../environment.js").MemoryEnvironmentOutput;
|
|
78
74
|
}>;
|
|
79
75
|
export declare function buildAssetsDirectory(output: {
|
|
80
76
|
files: Record<string, string>;
|
|
@@ -3,13 +3,6 @@ import type { PersistedOptions } from '../config-file.js';
|
|
|
3
3
|
export declare function createPackageAdditions(originalPackageJson: Record<string, any>, currentPackageJson: Record<string, any>): Record<string, any>;
|
|
4
4
|
export declare function createAppOptionsFromPersisted(json: PersistedOptions): Promise<Options>;
|
|
5
5
|
export declare function createSerializedOptionsFromPersisted(json: PersistedOptions): SerializedOptions;
|
|
6
|
-
export declare function runCreateApp(options: Required<Options>): Promise<
|
|
7
|
-
files: Record<string, string>;
|
|
8
|
-
deletedFiles: Array<string>;
|
|
9
|
-
commands: Array<{
|
|
10
|
-
command: string;
|
|
11
|
-
args: Array<string>;
|
|
12
|
-
}>;
|
|
13
|
-
}>;
|
|
6
|
+
export declare function runCreateApp(options: Required<Options>): Promise<import("../environment.js").MemoryEnvironmentOutput>;
|
|
14
7
|
export declare function compareFilesRecursively(path: string, ignore: (filePath: string) => boolean, original: Record<string, string>, changedFiles: Record<string, string>): Promise<void>;
|
|
15
8
|
export declare function readCurrentProjectOptions(environment: Environment): Promise<PersistedOptions>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Environment } from './types.js';
|
|
2
|
+
export interface MemoryEnvironmentOutput {
|
|
3
|
+
files: Record<string, string>;
|
|
4
|
+
deletedFiles: Array<string>;
|
|
5
|
+
commands: Array<{
|
|
6
|
+
command: string;
|
|
7
|
+
args: Array<string>;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
2
10
|
export declare function createDefaultEnvironment(): Environment;
|
|
3
11
|
export declare function createMemoryEnvironment(returnPathsRelativeTo?: string): {
|
|
4
12
|
environment: Environment;
|
|
5
|
-
output:
|
|
6
|
-
files: Record<string, string>;
|
|
7
|
-
deletedFiles: Array<string>;
|
|
8
|
-
commands: Array<{
|
|
9
|
-
command: string;
|
|
10
|
-
args: Array<string>;
|
|
11
|
-
}>;
|
|
12
|
-
};
|
|
13
|
+
output: MemoryEnvironmentOutput;
|
|
13
14
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createApp } from './create-app.js';
|
|
2
|
+
export { computeAttribution } from './attribution.js';
|
|
2
3
|
export { addToApp } from './add-to-app.js';
|
|
3
|
-
export { finalizeAddOns, getAllAddOns, populateAddOnOptionsDefaults } from './add-ons.js';
|
|
4
|
+
export { finalizeAddOns, getAllAddOns, populateAddOnOptionsDefaults, } from './add-ons.js';
|
|
4
5
|
export { loadRemoteAddOn } from './custom-add-ons/add-on.js';
|
|
5
6
|
export { loadStarter } from './custom-add-ons/starter.js';
|
|
6
7
|
export { createMemoryEnvironment, createDefaultEnvironment, } from './environment.js';
|
|
@@ -16,6 +17,8 @@ export { createAppOptionsFromPersisted, createSerializedOptionsFromPersisted, }
|
|
|
16
17
|
export { createSerializedOptions } from './options.js';
|
|
17
18
|
export { getRawRegistry, getRegistry, getRegistryAddOns, getRegistryStarters, } from './registry.js';
|
|
18
19
|
export { StarterCompiledSchema, StatusEvent, StatusStepType, StopEvent, AddOnCompiledSchema, AddOnInfoSchema, IntegrationSchema, } from './types.js';
|
|
19
|
-
export type { AddOn, AddOnOption, AddOnOptions, AddOnSelectOption, AddOnSelection, Environment, FileBundleHandler, Framework, FrameworkDefinition, Options, SerializedOptions, Starter, StarterCompiled, } from './types.js';
|
|
20
|
+
export type { AddOn, AddOnOption, AddOnOptions, AddOnSelectOption, AddOnSelection, Environment, FileBundleHandler, Framework, FrameworkDefinition, Options, SerializedOptions, Starter, StarterCompiled, LineAttribution, FileProvenance, AttributedFile, DependencyAttribution, } from './types.js';
|
|
21
|
+
export type { AttributionInput, AttributionOutput } from './attribution.js';
|
|
22
|
+
export type { MemoryEnvironmentOutput } from './environment.js';
|
|
20
23
|
export type { PersistedOptions } from './config-file.js';
|
|
21
24
|
export type { PackageManager } from './package-manager.js';
|
package/dist/types/types.d.ts
CHANGED
|
@@ -114,6 +114,9 @@ export declare const AddOnBaseSchema: z.ZodObject<{
|
|
|
114
114
|
warning: z.ZodOptional<z.ZodString>;
|
|
115
115
|
tailwind: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
116
116
|
type: z.ZodEnum<["add-on", "example", "starter", "toolchain", "deployment"]>;
|
|
117
|
+
category: z.ZodOptional<z.ZodEnum<["tanstack", "database", "orm", "auth", "deploy", "styling", "monitoring", "cms", "api", "i18n", "tooling", "other"]>>;
|
|
118
|
+
exclusive: z.ZodOptional<z.ZodArray<z.ZodEnum<["orm", "auth", "deploy", "database", "linter"]>, "many">>;
|
|
119
|
+
color: z.ZodOptional<z.ZodString>;
|
|
117
120
|
priority: z.ZodOptional<z.ZodNumber>;
|
|
118
121
|
command: z.ZodOptional<z.ZodObject<{
|
|
119
122
|
command: z.ZodString;
|
|
@@ -222,6 +225,9 @@ export declare const AddOnBaseSchema: z.ZodObject<{
|
|
|
222
225
|
link?: string | undefined;
|
|
223
226
|
license?: string | undefined;
|
|
224
227
|
warning?: string | undefined;
|
|
228
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
229
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
230
|
+
color?: string | undefined;
|
|
225
231
|
priority?: number | undefined;
|
|
226
232
|
routes?: {
|
|
227
233
|
path: string;
|
|
@@ -267,6 +273,9 @@ export declare const AddOnBaseSchema: z.ZodObject<{
|
|
|
267
273
|
license?: string | undefined;
|
|
268
274
|
warning?: string | undefined;
|
|
269
275
|
tailwind?: boolean | undefined;
|
|
276
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
277
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
278
|
+
color?: string | undefined;
|
|
270
279
|
priority?: number | undefined;
|
|
271
280
|
routes?: {
|
|
272
281
|
path: string;
|
|
@@ -297,6 +306,9 @@ export declare const StarterSchema: z.ZodObject<{
|
|
|
297
306
|
license: z.ZodOptional<z.ZodString>;
|
|
298
307
|
warning: z.ZodOptional<z.ZodString>;
|
|
299
308
|
type: z.ZodEnum<["add-on", "example", "starter", "toolchain", "deployment"]>;
|
|
309
|
+
category: z.ZodOptional<z.ZodEnum<["tanstack", "database", "orm", "auth", "deploy", "styling", "monitoring", "cms", "api", "i18n", "tooling", "other"]>>;
|
|
310
|
+
exclusive: z.ZodOptional<z.ZodArray<z.ZodEnum<["orm", "auth", "deploy", "database", "linter"]>, "many">>;
|
|
311
|
+
color: z.ZodOptional<z.ZodString>;
|
|
300
312
|
priority: z.ZodOptional<z.ZodNumber>;
|
|
301
313
|
command: z.ZodOptional<z.ZodObject<{
|
|
302
314
|
command: z.ZodString;
|
|
@@ -414,6 +426,9 @@ export declare const StarterSchema: z.ZodObject<{
|
|
|
414
426
|
link?: string | undefined;
|
|
415
427
|
license?: string | undefined;
|
|
416
428
|
warning?: string | undefined;
|
|
429
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
430
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
431
|
+
color?: string | undefined;
|
|
417
432
|
priority?: number | undefined;
|
|
418
433
|
routes?: {
|
|
419
434
|
path: string;
|
|
@@ -463,6 +478,9 @@ export declare const StarterSchema: z.ZodObject<{
|
|
|
463
478
|
link?: string | undefined;
|
|
464
479
|
license?: string | undefined;
|
|
465
480
|
warning?: string | undefined;
|
|
481
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
482
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
483
|
+
color?: string | undefined;
|
|
466
484
|
priority?: number | undefined;
|
|
467
485
|
routes?: {
|
|
468
486
|
path: string;
|
|
@@ -494,6 +512,9 @@ export declare const StarterCompiledSchema: z.ZodObject<{
|
|
|
494
512
|
license: z.ZodOptional<z.ZodString>;
|
|
495
513
|
warning: z.ZodOptional<z.ZodString>;
|
|
496
514
|
type: z.ZodEnum<["add-on", "example", "starter", "toolchain", "deployment"]>;
|
|
515
|
+
category: z.ZodOptional<z.ZodEnum<["tanstack", "database", "orm", "auth", "deploy", "styling", "monitoring", "cms", "api", "i18n", "tooling", "other"]>>;
|
|
516
|
+
exclusive: z.ZodOptional<z.ZodArray<z.ZodEnum<["orm", "auth", "deploy", "database", "linter"]>, "many">>;
|
|
517
|
+
color: z.ZodOptional<z.ZodString>;
|
|
497
518
|
priority: z.ZodOptional<z.ZodNumber>;
|
|
498
519
|
command: z.ZodOptional<z.ZodObject<{
|
|
499
520
|
command: z.ZodString;
|
|
@@ -616,6 +637,9 @@ export declare const StarterCompiledSchema: z.ZodObject<{
|
|
|
616
637
|
link?: string | undefined;
|
|
617
638
|
license?: string | undefined;
|
|
618
639
|
warning?: string | undefined;
|
|
640
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
641
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
642
|
+
color?: string | undefined;
|
|
619
643
|
priority?: number | undefined;
|
|
620
644
|
routes?: {
|
|
621
645
|
path: string;
|
|
@@ -667,6 +691,9 @@ export declare const StarterCompiledSchema: z.ZodObject<{
|
|
|
667
691
|
link?: string | undefined;
|
|
668
692
|
license?: string | undefined;
|
|
669
693
|
warning?: string | undefined;
|
|
694
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
695
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
696
|
+
color?: string | undefined;
|
|
670
697
|
priority?: number | undefined;
|
|
671
698
|
routes?: {
|
|
672
699
|
path: string;
|
|
@@ -718,6 +745,9 @@ export declare const AddOnInfoSchema: z.ZodObject<{
|
|
|
718
745
|
warning: z.ZodOptional<z.ZodString>;
|
|
719
746
|
tailwind: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
720
747
|
type: z.ZodEnum<["add-on", "example", "starter", "toolchain", "deployment"]>;
|
|
748
|
+
category: z.ZodOptional<z.ZodEnum<["tanstack", "database", "orm", "auth", "deploy", "styling", "monitoring", "cms", "api", "i18n", "tooling", "other"]>>;
|
|
749
|
+
exclusive: z.ZodOptional<z.ZodArray<z.ZodEnum<["orm", "auth", "deploy", "database", "linter"]>, "many">>;
|
|
750
|
+
color: z.ZodOptional<z.ZodString>;
|
|
721
751
|
priority: z.ZodOptional<z.ZodNumber>;
|
|
722
752
|
command: z.ZodOptional<z.ZodObject<{
|
|
723
753
|
command: z.ZodString;
|
|
@@ -851,6 +881,9 @@ export declare const AddOnInfoSchema: z.ZodObject<{
|
|
|
851
881
|
link?: string | undefined;
|
|
852
882
|
license?: string | undefined;
|
|
853
883
|
warning?: string | undefined;
|
|
884
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
885
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
886
|
+
color?: string | undefined;
|
|
854
887
|
priority?: number | undefined;
|
|
855
888
|
routes?: {
|
|
856
889
|
path: string;
|
|
@@ -906,6 +939,9 @@ export declare const AddOnInfoSchema: z.ZodObject<{
|
|
|
906
939
|
license?: string | undefined;
|
|
907
940
|
warning?: string | undefined;
|
|
908
941
|
tailwind?: boolean | undefined;
|
|
942
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
943
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
944
|
+
color?: string | undefined;
|
|
909
945
|
priority?: number | undefined;
|
|
910
946
|
routes?: {
|
|
911
947
|
path: string;
|
|
@@ -945,6 +981,9 @@ export declare const AddOnCompiledSchema: z.ZodObject<{
|
|
|
945
981
|
warning: z.ZodOptional<z.ZodString>;
|
|
946
982
|
tailwind: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
947
983
|
type: z.ZodEnum<["add-on", "example", "starter", "toolchain", "deployment"]>;
|
|
984
|
+
category: z.ZodOptional<z.ZodEnum<["tanstack", "database", "orm", "auth", "deploy", "styling", "monitoring", "cms", "api", "i18n", "tooling", "other"]>>;
|
|
985
|
+
exclusive: z.ZodOptional<z.ZodArray<z.ZodEnum<["orm", "auth", "deploy", "database", "linter"]>, "many">>;
|
|
986
|
+
color: z.ZodOptional<z.ZodString>;
|
|
948
987
|
priority: z.ZodOptional<z.ZodNumber>;
|
|
949
988
|
command: z.ZodOptional<z.ZodObject<{
|
|
950
989
|
command: z.ZodString;
|
|
@@ -1084,6 +1123,9 @@ export declare const AddOnCompiledSchema: z.ZodObject<{
|
|
|
1084
1123
|
link?: string | undefined;
|
|
1085
1124
|
license?: string | undefined;
|
|
1086
1125
|
warning?: string | undefined;
|
|
1126
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
1127
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
1128
|
+
color?: string | undefined;
|
|
1087
1129
|
priority?: number | undefined;
|
|
1088
1130
|
routes?: {
|
|
1089
1131
|
path: string;
|
|
@@ -1142,6 +1184,9 @@ export declare const AddOnCompiledSchema: z.ZodObject<{
|
|
|
1142
1184
|
license?: string | undefined;
|
|
1143
1185
|
warning?: string | undefined;
|
|
1144
1186
|
tailwind?: boolean | undefined;
|
|
1187
|
+
category?: "other" | "tanstack" | "database" | "orm" | "auth" | "deploy" | "styling" | "monitoring" | "cms" | "api" | "i18n" | "tooling" | undefined;
|
|
1188
|
+
exclusive?: ("database" | "orm" | "auth" | "deploy" | "linter")[] | undefined;
|
|
1189
|
+
color?: string | undefined;
|
|
1145
1190
|
priority?: number | undefined;
|
|
1146
1191
|
routes?: {
|
|
1147
1192
|
path: string;
|
|
@@ -1279,4 +1324,31 @@ type UIEnvironment = {
|
|
|
1279
1324
|
confirm: (message: string) => Promise<boolean>;
|
|
1280
1325
|
};
|
|
1281
1326
|
export type Environment = ProjectEnvironment & FileEnvironment & UIEnvironment;
|
|
1327
|
+
export interface LineAttribution {
|
|
1328
|
+
line: number;
|
|
1329
|
+
sourceId: string;
|
|
1330
|
+
sourceName: string;
|
|
1331
|
+
type: 'original' | 'injected';
|
|
1332
|
+
}
|
|
1333
|
+
export interface FileProvenance {
|
|
1334
|
+
source: 'framework' | 'add-on' | 'starter';
|
|
1335
|
+
sourceId: string;
|
|
1336
|
+
sourceName: string;
|
|
1337
|
+
}
|
|
1338
|
+
export interface AttributedFile {
|
|
1339
|
+
content: string;
|
|
1340
|
+
provenance: FileProvenance;
|
|
1341
|
+
lineAttributions: Array<LineAttribution>;
|
|
1342
|
+
}
|
|
1343
|
+
export interface DependencyAttribution {
|
|
1344
|
+
name: string;
|
|
1345
|
+
version: string;
|
|
1346
|
+
type: 'dependency' | 'devDependency';
|
|
1347
|
+
sourceId: string;
|
|
1348
|
+
sourceName: string;
|
|
1349
|
+
}
|
|
1350
|
+
export type IntegrationWithSource = Integration & {
|
|
1351
|
+
_sourceId: string;
|
|
1352
|
+
_sourceName: string;
|
|
1353
|
+
};
|
|
1282
1354
|
export {};
|
package/dist/types.js
CHANGED
|
@@ -24,6 +24,26 @@ export const AddOnBaseSchema = z.object({
|
|
|
24
24
|
warning: z.string().optional(),
|
|
25
25
|
tailwind: z.boolean().optional().default(true),
|
|
26
26
|
type: z.enum(['add-on', 'example', 'starter', 'toolchain', 'deployment']),
|
|
27
|
+
category: z
|
|
28
|
+
.enum([
|
|
29
|
+
'tanstack',
|
|
30
|
+
'database',
|
|
31
|
+
'orm',
|
|
32
|
+
'auth',
|
|
33
|
+
'deploy',
|
|
34
|
+
'styling',
|
|
35
|
+
'monitoring',
|
|
36
|
+
'cms',
|
|
37
|
+
'api',
|
|
38
|
+
'i18n',
|
|
39
|
+
'tooling',
|
|
40
|
+
'other',
|
|
41
|
+
])
|
|
42
|
+
.optional(),
|
|
43
|
+
exclusive: z
|
|
44
|
+
.array(z.enum(['orm', 'auth', 'deploy', 'database', 'linter']))
|
|
45
|
+
.optional(),
|
|
46
|
+
color: z.string().optional(),
|
|
27
47
|
priority: z.number().optional(),
|
|
28
48
|
command: z
|
|
29
49
|
.object({
|
package/package.json
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AddOn,
|
|
3
|
+
AttributedFile,
|
|
4
|
+
DependencyAttribution,
|
|
5
|
+
FileProvenance,
|
|
6
|
+
Framework,
|
|
7
|
+
Integration,
|
|
8
|
+
IntegrationWithSource,
|
|
9
|
+
LineAttribution,
|
|
10
|
+
Starter,
|
|
11
|
+
} from './types.js'
|
|
12
|
+
|
|
13
|
+
export interface AttributionInput {
|
|
14
|
+
framework: Framework
|
|
15
|
+
chosenAddOns: Array<AddOn>
|
|
16
|
+
starter?: Starter
|
|
17
|
+
files: Record<string, string>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AttributionOutput {
|
|
21
|
+
attributedFiles: Record<string, AttributedFile>
|
|
22
|
+
dependencies: Array<DependencyAttribution>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Source = { sourceId: string; sourceName: string }
|
|
26
|
+
|
|
27
|
+
// A pattern to search for in file content, with its source add-on
|
|
28
|
+
interface Injection {
|
|
29
|
+
matches: (line: string) => boolean
|
|
30
|
+
appliesTo: (filePath: string) => boolean
|
|
31
|
+
source: Source
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizePath(path: string): string {
|
|
35
|
+
let p = path.startsWith('./') ? path.slice(2) : path
|
|
36
|
+
p = p.replace(/\.ejs$/, '').replace(/_dot_/g, '.')
|
|
37
|
+
const match = p.match(/^(.+\/)?__([^_]+)__(.+)$/)
|
|
38
|
+
return match ? (match[1] || '') + match[3] : p
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function getFileProvenance(
|
|
42
|
+
filePath: string,
|
|
43
|
+
framework: Framework,
|
|
44
|
+
addOns: Array<AddOn>,
|
|
45
|
+
starter?: Starter,
|
|
46
|
+
): Promise<FileProvenance | null> {
|
|
47
|
+
const target = filePath.startsWith('./') ? filePath.slice(2) : filePath
|
|
48
|
+
|
|
49
|
+
if (starter) {
|
|
50
|
+
const files = await starter.getFiles()
|
|
51
|
+
if (files.some((f: string) => normalizePath(f) === target)) {
|
|
52
|
+
return {
|
|
53
|
+
source: 'starter',
|
|
54
|
+
sourceId: starter.id,
|
|
55
|
+
sourceName: starter.name,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Order add-ons by type then phase (matches writeFiles order), check in reverse
|
|
61
|
+
const typeOrder = ['add-on', 'example', 'toolchain', 'deployment']
|
|
62
|
+
const phaseOrder = ['setup', 'add-on', 'example']
|
|
63
|
+
const ordered = typeOrder.flatMap((type) =>
|
|
64
|
+
phaseOrder.flatMap((phase) =>
|
|
65
|
+
addOns.filter((a) => a.phase === phase && a.type === type),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for (let i = ordered.length - 1; i >= 0; i--) {
|
|
70
|
+
const files = await ordered[i].getFiles()
|
|
71
|
+
if (files.some((f: string) => normalizePath(f) === target)) {
|
|
72
|
+
return {
|
|
73
|
+
source: 'add-on',
|
|
74
|
+
sourceId: ordered[i].id,
|
|
75
|
+
sourceName: ordered[i].name,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const frameworkFiles = await framework.getFiles()
|
|
81
|
+
if (frameworkFiles.some((f: string) => normalizePath(f) === target)) {
|
|
82
|
+
return {
|
|
83
|
+
source: 'framework',
|
|
84
|
+
sourceId: framework.id,
|
|
85
|
+
sourceName: framework.name,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Build injection patterns from integrations (for source files)
|
|
93
|
+
function integrationInjections(int: IntegrationWithSource): Array<Injection> {
|
|
94
|
+
const source = { sourceId: int._sourceId, sourceName: int._sourceName }
|
|
95
|
+
const injections: Array<Injection> = []
|
|
96
|
+
|
|
97
|
+
const appliesTo = (path: string) => {
|
|
98
|
+
if (int.type === 'vite-plugin') return path.includes('vite.config')
|
|
99
|
+
if (
|
|
100
|
+
int.type === 'provider' ||
|
|
101
|
+
int.type === 'root-provider' ||
|
|
102
|
+
int.type === 'devtools'
|
|
103
|
+
) {
|
|
104
|
+
return path.includes('__root') || path.includes('root.tsx')
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (int.import) {
|
|
110
|
+
const prefix = int.import.split(' from ')[0]
|
|
111
|
+
injections.push({
|
|
112
|
+
matches: (line) => line.includes(prefix),
|
|
113
|
+
appliesTo,
|
|
114
|
+
source,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const code = int.code || int.jsName
|
|
119
|
+
if (code) {
|
|
120
|
+
injections.push({
|
|
121
|
+
matches: (line) => line.includes(code),
|
|
122
|
+
appliesTo,
|
|
123
|
+
source,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return injections
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Build injection pattern from a dependency (for package.json)
|
|
131
|
+
function dependencyInjection(dep: DependencyAttribution): Injection {
|
|
132
|
+
return {
|
|
133
|
+
matches: (line) => line.includes(`"${dep.name}"`),
|
|
134
|
+
appliesTo: (path) => path.endsWith('package.json'),
|
|
135
|
+
source: { sourceId: dep.sourceId, sourceName: dep.sourceName },
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function computeAttribution(
|
|
140
|
+
input: AttributionInput,
|
|
141
|
+
): Promise<AttributionOutput> {
|
|
142
|
+
const { framework, chosenAddOns, starter, files } = input
|
|
143
|
+
|
|
144
|
+
// Collect integrations tagged with source
|
|
145
|
+
const integrations: Array<IntegrationWithSource> = chosenAddOns.flatMap(
|
|
146
|
+
(addOn) =>
|
|
147
|
+
(addOn.integrations || []).map((int: Integration) => ({
|
|
148
|
+
...int,
|
|
149
|
+
_sourceId: addOn.id,
|
|
150
|
+
_sourceName: addOn.name,
|
|
151
|
+
})),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// Collect dependencies from add-ons (from packageAdditions or packageTemplate)
|
|
155
|
+
const dependencies: Array<DependencyAttribution> = chosenAddOns.flatMap(
|
|
156
|
+
(addOn) => {
|
|
157
|
+
const result: Array<DependencyAttribution> = []
|
|
158
|
+
const source = { sourceId: addOn.id, sourceName: addOn.name }
|
|
159
|
+
|
|
160
|
+
const addDeps = (
|
|
161
|
+
deps: Record<string, unknown> | undefined,
|
|
162
|
+
type: 'dependency' | 'devDependency',
|
|
163
|
+
) => {
|
|
164
|
+
if (!deps) return
|
|
165
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
166
|
+
if (typeof version === 'string') {
|
|
167
|
+
result.push({ name, version, type, ...source })
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// From static package.json
|
|
173
|
+
addDeps(addOn.packageAdditions?.dependencies, 'dependency')
|
|
174
|
+
addDeps(addOn.packageAdditions?.devDependencies, 'devDependency')
|
|
175
|
+
|
|
176
|
+
// From package.json.ejs template (strip EJS tags and parse)
|
|
177
|
+
if (addOn.packageTemplate) {
|
|
178
|
+
try {
|
|
179
|
+
const tmpl = JSON.parse(
|
|
180
|
+
addOn.packageTemplate.replace(/"[^"]*<%[^%]*%>[^"]*"/g, '""'),
|
|
181
|
+
)
|
|
182
|
+
addDeps(tmpl.dependencies, 'dependency')
|
|
183
|
+
addDeps(tmpl.devDependencies, 'devDependency')
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Build unified injection patterns from both integrations and dependencies
|
|
192
|
+
const injections: Array<Injection> = [
|
|
193
|
+
...integrations.flatMap(integrationInjections),
|
|
194
|
+
...dependencies.map(dependencyInjection),
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
const attributedFiles: Record<string, AttributedFile> = {}
|
|
198
|
+
|
|
199
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
200
|
+
const provenance = await getFileProvenance(
|
|
201
|
+
filePath,
|
|
202
|
+
framework,
|
|
203
|
+
chosenAddOns,
|
|
204
|
+
starter,
|
|
205
|
+
)
|
|
206
|
+
if (!provenance) continue
|
|
207
|
+
|
|
208
|
+
const lines = content.split('\n')
|
|
209
|
+
const relevant = injections.filter((inj) => inj.appliesTo(filePath))
|
|
210
|
+
|
|
211
|
+
// Find injected lines
|
|
212
|
+
const injectedLines = new Map<number, Source>()
|
|
213
|
+
for (const inj of relevant) {
|
|
214
|
+
lines.forEach((line, i) => {
|
|
215
|
+
if (inj.matches(line) && !injectedLines.has(i + 1)) {
|
|
216
|
+
injectedLines.set(i + 1, inj.source)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
attributedFiles[filePath] = {
|
|
222
|
+
content,
|
|
223
|
+
provenance,
|
|
224
|
+
lineAttributions: lines.map((_, i): LineAttribution => {
|
|
225
|
+
const lineNum = i + 1
|
|
226
|
+
const inj = injectedLines.get(lineNum)
|
|
227
|
+
return inj
|
|
228
|
+
? {
|
|
229
|
+
line: lineNum,
|
|
230
|
+
sourceId: inj.sourceId,
|
|
231
|
+
sourceName: inj.sourceName,
|
|
232
|
+
type: 'injected',
|
|
233
|
+
}
|
|
234
|
+
: {
|
|
235
|
+
line: lineNum,
|
|
236
|
+
sourceId: provenance.sourceId,
|
|
237
|
+
sourceName: provenance.sourceName,
|
|
238
|
+
type: 'original',
|
|
239
|
+
}
|
|
240
|
+
}),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { attributedFiles, dependencies }
|
|
245
|
+
}
|
package/src/environment.ts
CHANGED
|
@@ -21,6 +21,12 @@ import {
|
|
|
21
21
|
|
|
22
22
|
import type { Environment } from './types.js'
|
|
23
23
|
|
|
24
|
+
export interface MemoryEnvironmentOutput {
|
|
25
|
+
files: Record<string, string>
|
|
26
|
+
deletedFiles: Array<string>
|
|
27
|
+
commands: Array<{ command: string; args: Array<string> }>
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
export function createDefaultEnvironment(): Environment {
|
|
25
31
|
let errors: Array<string> = []
|
|
26
32
|
return {
|
|
@@ -46,7 +52,12 @@ export function createDefaultEnvironment(): Environment {
|
|
|
46
52
|
await mkdir(dirname(path), { recursive: true })
|
|
47
53
|
return writeFile(path, getBinaryFile(base64Contents) as string)
|
|
48
54
|
},
|
|
49
|
-
execute: async (
|
|
55
|
+
execute: async (
|
|
56
|
+
command: string,
|
|
57
|
+
args: Array<string>,
|
|
58
|
+
cwd: string,
|
|
59
|
+
options?: { inherit?: boolean },
|
|
60
|
+
) => {
|
|
50
61
|
try {
|
|
51
62
|
if (options?.inherit) {
|
|
52
63
|
// For commands that should show output directly to the user
|
|
@@ -106,14 +117,7 @@ export function createDefaultEnvironment(): Environment {
|
|
|
106
117
|
export function createMemoryEnvironment(returnPathsRelativeTo: string = '') {
|
|
107
118
|
const environment = createDefaultEnvironment()
|
|
108
119
|
|
|
109
|
-
const output: {
|
|
110
|
-
files: Record<string, string>
|
|
111
|
-
deletedFiles: Array<string>
|
|
112
|
-
commands: Array<{
|
|
113
|
-
command: string
|
|
114
|
-
args: Array<string>
|
|
115
|
-
}>
|
|
116
|
-
} = {
|
|
120
|
+
const output: MemoryEnvironmentOutput = {
|
|
117
121
|
files: {},
|
|
118
122
|
commands: [],
|
|
119
123
|
deletedFiles: [],
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export { createApp } from './create-app.js'
|
|
2
|
+
export { computeAttribution } from './attribution.js'
|
|
2
3
|
export { addToApp } from './add-to-app.js'
|
|
3
4
|
|
|
4
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
finalizeAddOns,
|
|
7
|
+
getAllAddOns,
|
|
8
|
+
populateAddOnOptionsDefaults,
|
|
9
|
+
} from './add-ons.js'
|
|
5
10
|
|
|
6
11
|
export { loadRemoteAddOn } from './custom-add-ons/add-on.js'
|
|
7
12
|
export { loadStarter } from './custom-add-ons/starter.js'
|
|
@@ -85,6 +90,12 @@ export type {
|
|
|
85
90
|
SerializedOptions,
|
|
86
91
|
Starter,
|
|
87
92
|
StarterCompiled,
|
|
93
|
+
LineAttribution,
|
|
94
|
+
FileProvenance,
|
|
95
|
+
AttributedFile,
|
|
96
|
+
DependencyAttribution,
|
|
88
97
|
} from './types.js'
|
|
98
|
+
export type { AttributionInput, AttributionOutput } from './attribution.js'
|
|
99
|
+
export type { MemoryEnvironmentOutput } from './environment.js'
|
|
89
100
|
export type { PersistedOptions } from './config-file.js'
|
|
90
101
|
export type { PackageManager } from './package-manager.js'
|
package/src/template-file.ts
CHANGED
|
@@ -9,7 +9,13 @@ import {
|
|
|
9
9
|
} from './package-manager.js'
|
|
10
10
|
import { relativePath } from './file-helpers.js'
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
AddOn,
|
|
14
|
+
Environment,
|
|
15
|
+
Integration,
|
|
16
|
+
IntegrationWithSource,
|
|
17
|
+
Options,
|
|
18
|
+
} from './types.js'
|
|
13
19
|
|
|
14
20
|
function convertDotFilesAndPaths(path: string) {
|
|
15
21
|
return path
|
|
@@ -50,11 +56,16 @@ export function createTemplateFile(environment: Environment, options: Options) {
|
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
// Collect integrations and tag them with source add-on for attribution
|
|
60
|
+
const integrations: Array<IntegrationWithSource> = []
|
|
54
61
|
for (const addOn of options.chosenAddOns) {
|
|
55
62
|
if (addOn.integrations) {
|
|
56
63
|
for (const integration of addOn.integrations) {
|
|
57
|
-
integrations.push(
|
|
64
|
+
integrations.push({
|
|
65
|
+
...integration,
|
|
66
|
+
_sourceId: addOn.id,
|
|
67
|
+
_sourceName: addOn.name,
|
|
68
|
+
})
|
|
58
69
|
}
|
|
59
70
|
}
|
|
60
71
|
}
|
package/src/types.ts
CHANGED
|
@@ -39,6 +39,26 @@ export const AddOnBaseSchema = z.object({
|
|
|
39
39
|
warning: z.string().optional(),
|
|
40
40
|
tailwind: z.boolean().optional().default(true),
|
|
41
41
|
type: z.enum(['add-on', 'example', 'starter', 'toolchain', 'deployment']),
|
|
42
|
+
category: z
|
|
43
|
+
.enum([
|
|
44
|
+
'tanstack',
|
|
45
|
+
'database',
|
|
46
|
+
'orm',
|
|
47
|
+
'auth',
|
|
48
|
+
'deploy',
|
|
49
|
+
'styling',
|
|
50
|
+
'monitoring',
|
|
51
|
+
'cms',
|
|
52
|
+
'api',
|
|
53
|
+
'i18n',
|
|
54
|
+
'tooling',
|
|
55
|
+
'other',
|
|
56
|
+
])
|
|
57
|
+
.optional(),
|
|
58
|
+
exclusive: z
|
|
59
|
+
.array(z.enum(['orm', 'auth', 'deploy', 'database', 'linter']))
|
|
60
|
+
.optional(),
|
|
61
|
+
color: z.string().optional(),
|
|
42
62
|
priority: z.number().optional(),
|
|
43
63
|
command: z
|
|
44
64
|
.object({
|
|
@@ -256,3 +276,37 @@ type UIEnvironment = {
|
|
|
256
276
|
}
|
|
257
277
|
|
|
258
278
|
export type Environment = ProjectEnvironment & FileEnvironment & UIEnvironment
|
|
279
|
+
|
|
280
|
+
// Attribution tracking types for file provenance
|
|
281
|
+
export interface LineAttribution {
|
|
282
|
+
line: number
|
|
283
|
+
sourceId: string
|
|
284
|
+
sourceName: string
|
|
285
|
+
type: 'original' | 'injected'
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface FileProvenance {
|
|
289
|
+
source: 'framework' | 'add-on' | 'starter'
|
|
290
|
+
sourceId: string
|
|
291
|
+
sourceName: string
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface AttributedFile {
|
|
295
|
+
content: string
|
|
296
|
+
provenance: FileProvenance
|
|
297
|
+
lineAttributions: Array<LineAttribution>
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface DependencyAttribution {
|
|
301
|
+
name: string
|
|
302
|
+
version: string
|
|
303
|
+
type: 'dependency' | 'devDependency'
|
|
304
|
+
sourceId: string
|
|
305
|
+
sourceName: string
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Integration with source add-on tracking (used in templates and attribution)
|
|
309
|
+
export type IntegrationWithSource = Integration & {
|
|
310
|
+
_sourceId: string
|
|
311
|
+
_sourceName: string
|
|
312
|
+
}
|