@parlor/scaffold 0.0.0-stage → 0.2.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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -2
  3. package/dist/add/addon.d.ts +103 -0
  4. package/dist/add/addon.d.ts.map +1 -0
  5. package/dist/add/addon.js +12 -0
  6. package/dist/add/addon.js.map +1 -0
  7. package/dist/add/plan.d.ts +72 -0
  8. package/dist/add/plan.d.ts.map +1 -0
  9. package/dist/add/plan.js +237 -0
  10. package/dist/add/plan.js.map +1 -0
  11. package/dist/add/run.d.ts +92 -0
  12. package/dist/add/run.d.ts.map +1 -0
  13. package/dist/add/run.js +167 -0
  14. package/dist/add/run.js.map +1 -0
  15. package/dist/add/transaction.d.ts +67 -0
  16. package/dist/add/transaction.d.ts.map +1 -0
  17. package/dist/add/transaction.js +181 -0
  18. package/dist/add/transaction.js.map +1 -0
  19. package/dist/addons/agent.d.ts +58 -0
  20. package/dist/addons/agent.d.ts.map +1 -0
  21. package/dist/addons/agent.js +50 -0
  22. package/dist/addons/agent.js.map +1 -0
  23. package/dist/addons/auth.d.ts +103 -0
  24. package/dist/addons/auth.d.ts.map +1 -0
  25. package/dist/addons/auth.js +93 -0
  26. package/dist/addons/auth.js.map +1 -0
  27. package/dist/addons/index.d.ts +159 -0
  28. package/dist/addons/index.d.ts.map +1 -0
  29. package/dist/addons/index.js +8 -0
  30. package/dist/addons/index.js.map +1 -0
  31. package/dist/catalog.d.ts +3 -0
  32. package/dist/catalog.d.ts.map +1 -0
  33. package/dist/catalog.js +23 -0
  34. package/dist/catalog.js.map +1 -0
  35. package/dist/create.d.ts +45 -0
  36. package/dist/create.d.ts.map +1 -0
  37. package/dist/create.js +162 -0
  38. package/dist/create.js.map +1 -0
  39. package/dist/docs.d.ts +12 -0
  40. package/dist/docs.d.ts.map +1 -0
  41. package/dist/docs.js +70 -0
  42. package/dist/docs.js.map +1 -0
  43. package/dist/edit/inspect.d.ts +15 -0
  44. package/dist/edit/inspect.d.ts.map +1 -0
  45. package/dist/edit/inspect.js +73 -0
  46. package/dist/edit/inspect.js.map +1 -0
  47. package/dist/edit/ops.d.ts +92 -0
  48. package/dist/edit/ops.d.ts.map +1 -0
  49. package/dist/edit/ops.js +516 -0
  50. package/dist/edit/ops.js.map +1 -0
  51. package/dist/edit/source.d.ts +24 -0
  52. package/dist/edit/source.d.ts.map +1 -0
  53. package/dist/edit/source.js +36 -0
  54. package/dist/edit/source.js.map +1 -0
  55. package/dist/edit/syntax.d.ts +39 -0
  56. package/dist/edit/syntax.d.ts.map +1 -0
  57. package/dist/edit/syntax.js +129 -0
  58. package/dist/edit/syntax.js.map +1 -0
  59. package/dist/errors.d.ts +5 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +5 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/files.d.ts +15 -0
  64. package/dist/files.d.ts.map +1 -0
  65. package/dist/files.js +65 -0
  66. package/dist/files.js.map +1 -0
  67. package/dist/fixtures.d.ts +28 -0
  68. package/dist/fixtures.d.ts.map +1 -0
  69. package/dist/fixtures.js +66 -0
  70. package/dist/fixtures.js.map +1 -0
  71. package/dist/git.d.ts +22 -0
  72. package/dist/git.d.ts.map +1 -0
  73. package/dist/git.js +56 -0
  74. package/dist/git.js.map +1 -0
  75. package/dist/identity.d.ts +9 -0
  76. package/dist/identity.d.ts.map +1 -0
  77. package/dist/identity.js +23 -0
  78. package/dist/identity.js.map +1 -0
  79. package/dist/index.d.ts +9 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +8 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/package-manager.d.ts +49 -0
  84. package/dist/package-manager.d.ts.map +1 -0
  85. package/dist/package-manager.js +118 -0
  86. package/dist/package-manager.js.map +1 -0
  87. package/dist/version.d.ts +6 -0
  88. package/dist/version.d.ts.map +1 -0
  89. package/dist/version.js +9 -0
  90. package/dist/version.js.map +1 -0
  91. package/package.json +46 -3
  92. package/templates/auth/auth.ts +19 -0
  93. package/templates/auth/schema/auth.ts +1 -0
  94. package/templates/auth/src/auth/client.ts +3 -0
  95. package/templates/auth/src/auth/sign-in-gate.tsx +17 -0
  96. package/templates/auth/src/auth/sign-out-button.tsx +17 -0
  97. package/templates/auth/src/routes/link.tsx +92 -0
  98. package/templates/auth/src/routes/sign-in.tsx +117 -0
  99. package/templates/default/_gitignore +7 -0
  100. package/templates/default/actions/index.ts +58 -0
  101. package/templates/default/actions/todos.spec.ts +38 -0
  102. package/templates/default/index.html +13 -0
  103. package/templates/default/migrations/0000_init.sql +9 -0
  104. package/templates/default/migrations/meta/0000_snapshot.json +87 -0
  105. package/templates/default/migrations/meta/_journal.json +13 -0
  106. package/templates/default/package.json +44 -0
  107. package/templates/default/parlor.config.ts +12 -0
  108. package/templates/default/schema/index.ts +1 -0
  109. package/templates/default/schema/todos.ts +13 -0
  110. package/templates/default/server.ts +13 -0
  111. package/templates/default/src/agent/agent-button.tsx +120 -0
  112. package/templates/default/src/agent/agent-sidebar.tsx +828 -0
  113. package/templates/default/src/agent/agent.css +5 -0
  114. package/templates/default/src/agent/confirmation-card.tsx +42 -0
  115. package/templates/default/src/agent/sidebar-view.ts +36 -0
  116. package/templates/default/src/agent/views.ts +9 -0
  117. package/templates/default/src/app.css +1 -0
  118. package/templates/default/src/header-actions.tsx +9 -0
  119. package/templates/default/src/main.tsx +27 -0
  120. package/templates/default/src/parlor.d.ts +9 -0
  121. package/templates/default/src/routeTree.gen.ts +59 -0
  122. package/templates/default/src/routes/__root.tsx +29 -0
  123. package/templates/default/src/routes/index.tsx +141 -0
  124. package/templates/default/tsconfig.json +8 -0
  125. package/templates/default/vitest.config.ts +5 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cychien
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,12 @@
1
- # Temporary Holding Version
1
+ # @parlor/scaffold
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ What `create-parlor` and `parlor add` share: the project templates, the add-on model, the syntax-tree edit engine, package manager detection, install and git. Installed by `create-parlor` and `@parlor/cli`; you do not depend on it directly.
4
+
5
+ ```ts
6
+ import { ADDON_NAMES, commandsFor } from "@parlor/scaffold";
7
+
8
+ const pnpm = commandsFor({ name: "pnpm", version: undefined });
9
+ console.log(ADDON_NAMES.map((addon) => pnpm.exec("parlor", ["add", addon])));
10
+ ```
11
+
12
+ Docs: https://cychien.github.io/parlor/reference/cli/
@@ -0,0 +1,103 @@
1
+ import type { CallSite, Op } from "../edit/ops.js";
2
+ /** Stands for `server.entry` from `parlor.config.ts`, wherever the app keeps it. */
3
+ export declare const SERVER_ENTRY: unique symbol;
4
+ export type ProjectFile = string | typeof SERVER_ENTRY;
5
+ /** The root route, with or without a typed router context. */
6
+ export declare const ROOT_ROUTE: CallSite;
7
+ /** Folders under `templates/` in this package. `default` is the full default app. */
8
+ export type TemplateName = "default" | "auth";
9
+ /**
10
+ * Where the project keeps what add-ons touch, from `parlor.config.ts`. Built by the caller
11
+ * (`parlor add` loads the config), so this package never loads it.
12
+ */
13
+ export interface ProjectLayout {
14
+ readonly rootDir: string;
15
+ readonly serverEntry: string;
16
+ readonly schemaDir: string;
17
+ readonly migrationsDir: string;
18
+ /** The generated route tree, `src/routeTree.gen.ts` unless `web.routeTree` moves it. */
19
+ readonly routeTree: string;
20
+ }
21
+ /** Payload files import each other by relative path, so they need the default layout. */
22
+ export interface LayoutRequirement {
23
+ readonly serverAtRoot?: true;
24
+ readonly schemaDir?: string;
25
+ readonly routesDir?: string;
26
+ }
27
+ /**
28
+ * Every file under `dir` of the template, copied to the same path in the project. A file that
29
+ * already exists is the developer's and is never overwritten.
30
+ */
31
+ export interface Payload {
32
+ readonly template: TemplateName;
33
+ readonly dir: string;
34
+ }
35
+ export interface FileEdit {
36
+ readonly file: ProjectFile;
37
+ readonly ops: readonly Op[];
38
+ }
39
+ /**
40
+ * Renders `<component />` once in the app's UI. Done when any module under `src/` imports
41
+ * `component` from `module`, wherever the developer put it, so the manual change for a missing
42
+ * `into` file satisfies the next run.
43
+ */
44
+ export interface Mount {
45
+ readonly component: string;
46
+ /** Project path of the module that exports `component`. */
47
+ readonly module: string;
48
+ readonly into: {
49
+ readonly file: string;
50
+ readonly parent: string;
51
+ };
52
+ }
53
+ /**
54
+ * A pin another installed package owns: `better-auth` is whatever the project's `@parlor/auth`
55
+ * declares, read from its manifest in `node_modules` after that package is installed.
56
+ */
57
+ export interface FromPackage {
58
+ readonly fromPackage: string;
59
+ readonly dependency: string;
60
+ }
61
+ export type DependencyVersion =
62
+ /** Exactly this package's version: every `@parlor/*` package releases with it. */
63
+ "framework"
64
+ /** An explicit range, for a third-party package nothing in the framework pins. */
65
+ | (string & {}) | FromPackage;
66
+ export interface EnvVar {
67
+ readonly name: string;
68
+ readonly example: string;
69
+ readonly description: string;
70
+ }
71
+ /**
72
+ * A step a framework tool runs after the files are in place. Each kind declares its outputs
73
+ * through `done`: the plan uses it to skip finished work, the transaction uses it to validate
74
+ * the staging copy before anything reaches the project.
75
+ */
76
+ export type Generation =
77
+ /** `parlor typegen`; done when the route tree lists every route id in `routes`. */
78
+ {
79
+ readonly kind: "route-tree";
80
+ readonly routes: readonly string[];
81
+ }
82
+ /** `parlor db generate --name <name>`; done when the latest snapshot has every table. */
83
+ | {
84
+ readonly kind: "migration";
85
+ readonly name: string;
86
+ readonly tables: readonly string[];
87
+ };
88
+ export interface Addon {
89
+ /** One line for `parlor add --help` and the docs. */
90
+ readonly summary: string;
91
+ readonly requires: LayoutRequirement;
92
+ readonly dependencies: Readonly<Record<string, DependencyVersion>>;
93
+ readonly files: readonly Payload[];
94
+ readonly edits: readonly FileEdit[];
95
+ readonly mounts: readonly Mount[];
96
+ /** Lines added to `.env.example` when missing. */
97
+ readonly env: readonly EnvVar[];
98
+ readonly generate: readonly Generation[];
99
+ /** Printed after a run that changed something. */
100
+ readonly notes: readonly string[];
101
+ }
102
+ export declare function defineAddon<const A extends Addon>(addon: A): A;
103
+ //# sourceMappingURL=addon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addon.d.ts","sourceRoot":"","sources":["../../src/add/addon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /** Stands for `server.entry` from `parlor.config.ts`, wherever the app keeps it. */
2
+ export const SERVER_ENTRY = Symbol("server entry");
3
+ /** The root route, with or without a typed router context. */
4
+ export const ROOT_ROUTE = {
5
+ callee: "createRootRoute",
6
+ or: ["createRootRouteWithContext"],
7
+ from: "@tanstack/react-router",
8
+ };
9
+ export function defineAddon(addon) {
10
+ return addon;
11
+ }
12
+ //# sourceMappingURL=addon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addon.js","sourceRoot":"","sources":["../../src/add/addon.ts"],"names":[],"mappings":"AAEA,oFAAoF;AACpF,MAAM,CAAC,MAAM,YAAY,GAAkB,MAAM,CAAC,cAAc,CAAC,CAAC;AAGlE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,UAAU,GAAa;IAClC,MAAM,EAAE,iBAAiB;IACzB,EAAE,EAAE,CAAC,4BAA4B,CAAC;IAClC,IAAI,EAAE,wBAAwB;CAC/B,CAAC;AAuGF,MAAM,UAAU,WAAW,CAAwB,KAAQ;IACzD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { CallSite, Op } from \"../edit/ops.js\";\n\n/** Stands for `server.entry` from `parlor.config.ts`, wherever the app keeps it. */\nexport const SERVER_ENTRY: unique symbol = Symbol(\"server entry\");\nexport type ProjectFile = string | typeof SERVER_ENTRY;\n\n/** The root route, with or without a typed router context. */\nexport const ROOT_ROUTE: CallSite = {\n callee: \"createRootRoute\",\n or: [\"createRootRouteWithContext\"],\n from: \"@tanstack/react-router\",\n};\n\n/** Folders under `templates/` in this package. `default` is the full default app. */\nexport type TemplateName = \"default\" | \"auth\";\n\n/**\n * Where the project keeps what add-ons touch, from `parlor.config.ts`. Built by the caller\n * (`parlor add` loads the config), so this package never loads it.\n */\nexport interface ProjectLayout {\n readonly rootDir: string;\n readonly serverEntry: string;\n readonly schemaDir: string;\n readonly migrationsDir: string;\n /** The generated route tree, `src/routeTree.gen.ts` unless `web.routeTree` moves it. */\n readonly routeTree: string;\n}\n\n/** Payload files import each other by relative path, so they need the default layout. */\nexport interface LayoutRequirement {\n readonly serverAtRoot?: true;\n readonly schemaDir?: string;\n readonly routesDir?: string;\n}\n\n/**\n * Every file under `dir` of the template, copied to the same path in the project. A file that\n * already exists is the developer's and is never overwritten.\n */\nexport interface Payload {\n readonly template: TemplateName;\n readonly dir: string;\n}\n\nexport interface FileEdit {\n readonly file: ProjectFile;\n readonly ops: readonly Op[];\n}\n\n/**\n * Renders `<component />` once in the app's UI. Done when any module under `src/` imports\n * `component` from `module`, wherever the developer put it, so the manual change for a missing\n * `into` file satisfies the next run.\n */\nexport interface Mount {\n readonly component: string;\n /** Project path of the module that exports `component`. */\n readonly module: string;\n readonly into: { readonly file: string; readonly parent: string };\n}\n\n/**\n * A pin another installed package owns: `better-auth` is whatever the project's `@parlor/auth`\n * declares, read from its manifest in `node_modules` after that package is installed.\n */\nexport interface FromPackage {\n readonly fromPackage: string;\n readonly dependency: string;\n}\n\nexport type DependencyVersion =\n /** Exactly this package's version: every `@parlor/*` package releases with it. */\n | \"framework\"\n /** An explicit range, for a third-party package nothing in the framework pins. */\n | (string & {})\n | FromPackage;\n\nexport interface EnvVar {\n readonly name: string;\n readonly example: string;\n readonly description: string;\n}\n\n/**\n * A step a framework tool runs after the files are in place. Each kind declares its outputs\n * through `done`: the plan uses it to skip finished work, the transaction uses it to validate\n * the staging copy before anything reaches the project.\n */\nexport type Generation =\n /** `parlor typegen`; done when the route tree lists every route id in `routes`. */\n | { readonly kind: \"route-tree\"; readonly routes: readonly string[] }\n /** `parlor db generate --name <name>`; done when the latest snapshot has every table. */\n | {\n readonly kind: \"migration\";\n readonly name: string;\n readonly tables: readonly string[];\n };\n\nexport interface Addon {\n /** One line for `parlor add --help` and the docs. */\n readonly summary: string;\n readonly requires: LayoutRequirement;\n readonly dependencies: Readonly<Record<string, DependencyVersion>>;\n readonly files: readonly Payload[];\n readonly edits: readonly FileEdit[];\n readonly mounts: readonly Mount[];\n /** Lines added to `.env.example` when missing. */\n readonly env: readonly EnvVar[];\n readonly generate: readonly Generation[];\n /** Printed after a run that changed something. */\n readonly notes: readonly string[];\n}\n\nexport function defineAddon<const A extends Addon>(addon: A): A {\n return addon;\n}\n"]}
@@ -0,0 +1,72 @@
1
+ import { type AddonName } from "../addons/index.js";
2
+ import type { TemplateFiles } from "../files.js";
3
+ import { type FromPackage, type Generation, type ProjectLayout } from "./addon.js";
4
+ /** The fields of a `package.json` the engine reads. */
5
+ export interface PackageManifest {
6
+ readonly name?: string;
7
+ readonly version?: string;
8
+ readonly dependencies?: Readonly<Record<string, string>>;
9
+ readonly devDependencies?: Readonly<Record<string, string>>;
10
+ }
11
+ /** Reads the project as it is on disk; tests pass an in-memory map. */
12
+ export interface ProjectFiles {
13
+ read(path: string): Promise<string | undefined>;
14
+ /** Project-relative paths of every file under `dir`, recursively, skipping `node_modules`. */
15
+ list(dir: string): Promise<readonly string[]>;
16
+ /** The manifest of `name` as installed in the project's `node_modules`; `undefined` when absent. */
17
+ manifest(name: string): Promise<PackageManifest | undefined>;
18
+ }
19
+ export interface PlannedWrite {
20
+ readonly path: string;
21
+ readonly content: string;
22
+ readonly change: "create" | "update";
23
+ /** One short line per op or payload, for the printed plan: "auth: createAuth(database)". */
24
+ readonly summary: readonly string[];
25
+ }
26
+ /**
27
+ * A dependency the package manager adds; it owns `package.json` and the lockfile, so neither
28
+ * is a planned write. `version` stays a `FromPackage` until that package is installed, which is
29
+ * why install runs in two rounds.
30
+ */
31
+ export interface PlannedDependency {
32
+ readonly name: string;
33
+ readonly version: string | FromPackage;
34
+ readonly dev: boolean;
35
+ }
36
+ /** What to change by hand, for D4: the file, why the tool would not, and the exact change. */
37
+ export interface ManualChange {
38
+ readonly file: string;
39
+ readonly reason: string;
40
+ readonly change: string;
41
+ }
42
+ export type AddPlan = {
43
+ readonly kind: "done";
44
+ readonly addons: readonly AddonName[];
45
+ }
46
+ /** Nothing may be written: the developer makes `manual` by hand and runs the command again. */
47
+ | {
48
+ readonly kind: "blocked";
49
+ readonly addons: readonly AddonName[];
50
+ readonly manual: readonly ManualChange[];
51
+ } | {
52
+ readonly kind: "ready";
53
+ readonly addons: readonly AddonName[];
54
+ /** Every changed file with its whole new content; nothing is written while planning. */
55
+ readonly writes: readonly PlannedWrite[];
56
+ readonly dependencies: readonly PlannedDependency[];
57
+ readonly generate: readonly Generation[];
58
+ readonly notes: readonly string[];
59
+ };
60
+ /** `fromPackage`'s own pin of `dependency`, as the project has it installed. */
61
+ export declare function resolveFromPackage(version: FromPackage, project: ProjectFiles): Promise<string | undefined>;
62
+ /**
63
+ * Pure apart from reads. For every add-on, in order, each step is `done`, an edit, or unsafe;
64
+ * any unsafe step makes the plan `blocked` with every manual change, not just the first.
65
+ */
66
+ export declare function planAddons(layout: ProjectLayout, addons: readonly AddonName[], project: ProjectFiles, templates: TemplateFiles): Promise<AddPlan>;
67
+ /**
68
+ * Whether a generation's declared outputs are in `project`: the route tree lists every route
69
+ * id; the latest drizzle snapshot (the last entry of `meta/_journal.json`) has every table.
70
+ */
71
+ export declare function generationDone(layout: ProjectLayout, generation: Generation, project: ProjectFiles): Promise<boolean>;
72
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/add/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,237 @@
1
+ import { dirname, relative } from "node:path";
2
+ import { ADDONS } from "../addons/index.js";
3
+ import { editSource, evaluateOp, manualCode, opSummary, } from "../edit/ops.js";
4
+ import { parseSource } from "../edit/source.js";
5
+ import { importedName } from "../edit/syntax.js";
6
+ import { FRAMEWORK_VERSION } from "../version.js";
7
+ import { SERVER_ENTRY, } from "./addon.js";
8
+ /** `fromPackage`'s own pin of `dependency`, as the project has it installed. */
9
+ export async function resolveFromPackage(version, project) {
10
+ const manifest = await project.manifest(version.fromPackage);
11
+ return (manifest?.dependencies?.[version.dependency] ??
12
+ manifest?.devDependencies?.[version.dependency]);
13
+ }
14
+ const relativeImport = (from, module) => {
15
+ const path = relative(dirname(from), module).replace(/\.tsx?$/, ".js");
16
+ return path.startsWith(".") ? path : `./${path}`;
17
+ };
18
+ const isModule = (path) => /\.(?:ts|tsx)$/.test(path) && !path.endsWith(".d.ts");
19
+ /**
20
+ * Pure apart from reads. For every add-on, in order, each step is `done`, an edit, or unsafe;
21
+ * any unsafe step makes the plan `blocked` with every manual change, not just the first.
22
+ */
23
+ export async function planAddons(layout, addons, project, templates) {
24
+ const drafts = new Map();
25
+ const manual = [];
26
+ const dependencies = [];
27
+ const generate = [];
28
+ const notes = [];
29
+ const read = async (path) => drafts.get(path)?.content ?? (await project.read(path));
30
+ const write = async (path, content, summary) => {
31
+ const draft = drafts.get(path);
32
+ if (draft) {
33
+ draft.content = content;
34
+ draft.summary.push(...summary);
35
+ return;
36
+ }
37
+ const exists = (await project.read(path)) !== undefined;
38
+ drafts.set(path, {
39
+ content,
40
+ change: exists ? "update" : "create",
41
+ summary: [...summary],
42
+ });
43
+ };
44
+ const manifest = JSON.parse((await project.read("package.json")) ?? "{}");
45
+ const fingerprint = () => JSON.stringify([
46
+ [...drafts].map(([path, draft]) => [path, draft.content]),
47
+ dependencies,
48
+ generate,
49
+ ]);
50
+ const listed = (name) => Boolean(manifest.dependencies?.[name] ?? manifest.devDependencies?.[name]);
51
+ for (const name of addons) {
52
+ const addon = ADDONS[name];
53
+ const before = fingerprint();
54
+ const requirement = unmetRequirement(name, addon, layout);
55
+ if (requirement) {
56
+ manual.push(requirement);
57
+ continue;
58
+ }
59
+ for (const payload of addon.files) {
60
+ for (const path of await templates.list(payload.template, payload.dir)) {
61
+ if ((await read(path)) === undefined) {
62
+ await write(path, await templates.read(payload.template, path), []);
63
+ }
64
+ }
65
+ }
66
+ const edits = new Map();
67
+ const addEdit = (file, ops) => edits.set(file, [...(edits.get(file) ?? []), ...ops]);
68
+ for (const fileEdit of addon.edits) {
69
+ addEdit(fileEdit.file === SERVER_ENTRY ? layout.serverEntry : fileEdit.file, fileEdit.ops);
70
+ }
71
+ for (const mount of addon.mounts) {
72
+ if (await mounted(mount, project, drafts))
73
+ continue;
74
+ addEdit(mount.into.file, [
75
+ {
76
+ kind: "import",
77
+ names: [mount.component],
78
+ from: relativeImport(mount.into.file, mount.module),
79
+ },
80
+ { kind: "jsx-child", component: mount.into.parent, element: mount.component },
81
+ ]);
82
+ }
83
+ for (const [file, ops] of edits) {
84
+ const text = await read(file);
85
+ if (text === undefined) {
86
+ manual.push({
87
+ file,
88
+ reason: `${file} is missing.`,
89
+ change: ops.map((op) => manualCode(op)).join("\n\n"),
90
+ });
91
+ continue;
92
+ }
93
+ const source = parseSource(file, text);
94
+ const outcome = editSource(source, ops);
95
+ if (outcome.status === "done")
96
+ continue;
97
+ if (outcome.status === "unsafe") {
98
+ manual.push(manualFor(file, ops, outcome.unsafe));
99
+ continue;
100
+ }
101
+ const summary = ops
102
+ .filter((op) => evaluateOp(source, op).status === "edit")
103
+ .map((op) => opSummary(source, op))
104
+ .filter((line) => line !== undefined);
105
+ await write(file, outcome.text, summary);
106
+ }
107
+ if (addon.env.length > 0) {
108
+ const current = (await read(ENV_EXAMPLE)) ?? "";
109
+ const missing = addon.env.filter((variable) => !new RegExp(`^${variable.name}=`, "m").test(current));
110
+ if (missing.length > 0) {
111
+ const lines = missing.map((variable) => `# ${variable.description}\n${variable.name}=${variable.example}\n`);
112
+ const separator = current === "" || current.endsWith("\n\n")
113
+ ? ""
114
+ : current.endsWith("\n")
115
+ ? "\n"
116
+ : "\n\n";
117
+ await write(ENV_EXAMPLE, `${current}${separator}${lines.join("\n")}`, missing.map((variable) => variable.name));
118
+ }
119
+ }
120
+ for (const [dependency, version] of Object.entries(addon.dependencies)) {
121
+ if (dependencies.some((planned) => planned.name === dependency))
122
+ continue;
123
+ if (listed(dependency))
124
+ continue;
125
+ dependencies.push({
126
+ name: dependency,
127
+ version: await resolveVersion(version, project),
128
+ dev: false,
129
+ });
130
+ }
131
+ for (const generation of addon.generate) {
132
+ if (!(await generationDone(layout, generation, project)))
133
+ generate.push(generation);
134
+ }
135
+ if (fingerprint() !== before)
136
+ notes.push(...addon.notes);
137
+ }
138
+ if (manual.length > 0)
139
+ return { kind: "blocked", addons, manual };
140
+ if (drafts.size === 0 && dependencies.length === 0 && generate.length === 0) {
141
+ return { kind: "done", addons };
142
+ }
143
+ return {
144
+ kind: "ready",
145
+ addons,
146
+ writes: [...drafts].map(([path, draft]) => ({ path, ...draft })),
147
+ dependencies,
148
+ generate,
149
+ notes,
150
+ };
151
+ }
152
+ const ENV_EXAMPLE = ".env.example";
153
+ function unmetRequirement(name, addon, layout) {
154
+ const { requires } = addon;
155
+ if (requires.serverAtRoot && layout.serverEntry.includes("/")) {
156
+ return {
157
+ file: layout.serverEntry,
158
+ reason: `${name} needs the server entry at the project root, and it is ${layout.serverEntry}.`,
159
+ change: `Move ${layout.serverEntry} to server.ts and set server.entry to "server.ts" in parlor.config.ts.`,
160
+ };
161
+ }
162
+ if (requires.schemaDir && layout.schemaDir !== requires.schemaDir) {
163
+ return {
164
+ file: "parlor.config.ts",
165
+ reason: `${name} needs the schema in ${requires.schemaDir}/, and it is in ${layout.schemaDir}.`,
166
+ change: `Set database.schema to "${requires.schemaDir}" in parlor.config.ts and move the tables there.`,
167
+ };
168
+ }
169
+ const routes = `${dirname(layout.routeTree)}/routes`;
170
+ if (requires.routesDir && routes !== requires.routesDir) {
171
+ return {
172
+ file: "parlor.config.ts",
173
+ reason: `${name} needs the routes in ${requires.routesDir}/, and they are in ${routes}/.`,
174
+ change: `Keep web.routeTree at its default so the routes live in ${requires.routesDir}/.`,
175
+ };
176
+ }
177
+ return undefined;
178
+ }
179
+ async function mounted(mount, project, drafts) {
180
+ const paths = new Set([
181
+ ...(await project.list("src")),
182
+ ...[...drafts.keys()].filter((path) => path.startsWith("src/")),
183
+ ]);
184
+ for (const path of paths) {
185
+ if (!isModule(path) || path === mount.module)
186
+ continue;
187
+ const text = drafts.get(path)?.content ?? (await project.read(path));
188
+ if (text === undefined || !text.includes(mount.component))
189
+ continue;
190
+ const source = parseSource(path, text);
191
+ if (importedName(source, mount.component, relativeImport(path, mount.module)))
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ function manualFor(file, ops, failed) {
197
+ const imports = ops.filter((op) => op.kind === "import").map((op) => manualCode(op));
198
+ const changes = failed
199
+ .filter((entry) => entry.op.kind !== "import")
200
+ .map((entry) => manualCode(entry.op));
201
+ return {
202
+ file,
203
+ reason: [...new Set(failed.map((entry) => entry.reason))].join(" "),
204
+ change: [...(imports.length > 0 ? [imports.join("\n")] : []), ...changes].join("\n\n"),
205
+ };
206
+ }
207
+ async function resolveVersion(version, project) {
208
+ if (version === "framework")
209
+ return FRAMEWORK_VERSION;
210
+ if (typeof version === "string")
211
+ return version;
212
+ return (await resolveFromPackage(version, project)) ?? version;
213
+ }
214
+ /**
215
+ * Whether a generation's declared outputs are in `project`: the route tree lists every route
216
+ * id; the latest drizzle snapshot (the last entry of `meta/_journal.json`) has every table.
217
+ */
218
+ export async function generationDone(layout, generation, project) {
219
+ if (generation.kind === "route-tree") {
220
+ const tree = await project.read(layout.routeTree);
221
+ return (tree !== undefined &&
222
+ generation.routes.every((route) => tree.includes(`"${route}"`)));
223
+ }
224
+ const journal = await project.read(`${layout.migrationsDir}/meta/_journal.json`);
225
+ if (journal === undefined)
226
+ return false;
227
+ const entries = JSON.parse(journal).entries ?? [];
228
+ const last = entries.at(-1);
229
+ if (!last)
230
+ return false;
231
+ const snapshot = await project.read(`${layout.migrationsDir}/meta/${String(last.idx).padStart(4, "0")}_snapshot.json`);
232
+ if (snapshot === undefined)
233
+ return false;
234
+ const tables = Object.values(JSON.parse(snapshot).tables ?? {}).map((table) => table.name);
235
+ return generation.tables.every((table) => tables.includes(table));
236
+ }
237
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/add/plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, relative } from \"node:path\";\n\nimport { ADDONS, type AddonName } from \"../addons/index.js\";\nimport {\n editSource,\n evaluateOp,\n manualCode,\n type Op,\n opSummary,\n type UnsafeOp,\n} from \"../edit/ops.js\";\nimport { parseSource } from \"../edit/source.js\";\nimport { importedName } from \"../edit/syntax.js\";\nimport type { TemplateFiles } from \"../files.js\";\nimport { FRAMEWORK_VERSION } from \"../version.js\";\nimport {\n type Addon,\n type DependencyVersion,\n type FromPackage,\n type Generation,\n type Mount,\n type ProjectLayout,\n SERVER_ENTRY,\n} from \"./addon.js\";\n\n/** The fields of a `package.json` the engine reads. */\nexport interface PackageManifest {\n readonly name?: string;\n readonly version?: string;\n readonly dependencies?: Readonly<Record<string, string>>;\n readonly devDependencies?: Readonly<Record<string, string>>;\n}\n\n/** Reads the project as it is on disk; tests pass an in-memory map. */\nexport interface ProjectFiles {\n read(path: string): Promise<string | undefined>;\n /** Project-relative paths of every file under `dir`, recursively, skipping `node_modules`. */\n list(dir: string): Promise<readonly string[]>;\n /** The manifest of `name` as installed in the project's `node_modules`; `undefined` when absent. */\n manifest(name: string): Promise<PackageManifest | undefined>;\n}\n\nexport interface PlannedWrite {\n readonly path: string;\n readonly content: string;\n readonly change: \"create\" | \"update\";\n /** One short line per op or payload, for the printed plan: \"auth: createAuth(database)\". */\n readonly summary: readonly string[];\n}\n\n/**\n * A dependency the package manager adds; it owns `package.json` and the lockfile, so neither\n * is a planned write. `version` stays a `FromPackage` until that package is installed, which is\n * why install runs in two rounds.\n */\nexport interface PlannedDependency {\n readonly name: string;\n readonly version: string | FromPackage;\n readonly dev: boolean;\n}\n\n/** What to change by hand, for D4: the file, why the tool would not, and the exact change. */\nexport interface ManualChange {\n readonly file: string;\n readonly reason: string;\n readonly change: string;\n}\n\nexport type AddPlan =\n | { readonly kind: \"done\"; readonly addons: readonly AddonName[] }\n /** Nothing may be written: the developer makes `manual` by hand and runs the command again. */\n | {\n readonly kind: \"blocked\";\n readonly addons: readonly AddonName[];\n readonly manual: readonly ManualChange[];\n }\n | {\n readonly kind: \"ready\";\n readonly addons: readonly AddonName[];\n /** Every changed file with its whole new content; nothing is written while planning. */\n readonly writes: readonly PlannedWrite[];\n readonly dependencies: readonly PlannedDependency[];\n readonly generate: readonly Generation[];\n readonly notes: readonly string[];\n };\n\n/** `fromPackage`'s own pin of `dependency`, as the project has it installed. */\nexport async function resolveFromPackage(\n version: FromPackage,\n project: ProjectFiles,\n): Promise<string | undefined> {\n const manifest = await project.manifest(version.fromPackage);\n return (\n manifest?.dependencies?.[version.dependency] ??\n manifest?.devDependencies?.[version.dependency]\n );\n}\n\nconst relativeImport = (from: string, module: string): string => {\n const path = relative(dirname(from), module).replace(/\\.tsx?$/, \".js\");\n return path.startsWith(\".\") ? path : `./${path}`;\n};\n\nconst isModule = (path: string) => /\\.(?:ts|tsx)$/.test(path) && !path.endsWith(\".d.ts\");\n\ninterface Draft {\n content: string;\n readonly change: \"create\" | \"update\";\n readonly summary: string[];\n}\n\n/**\n * Pure apart from reads. For every add-on, in order, each step is `done`, an edit, or unsafe;\n * any unsafe step makes the plan `blocked` with every manual change, not just the first.\n */\nexport async function planAddons(\n layout: ProjectLayout,\n addons: readonly AddonName[],\n project: ProjectFiles,\n templates: TemplateFiles,\n): Promise<AddPlan> {\n const drafts = new Map<string, Draft>();\n const manual: ManualChange[] = [];\n const dependencies: PlannedDependency[] = [];\n const generate: Generation[] = [];\n const notes: string[] = [];\n const read = async (path: string) =>\n drafts.get(path)?.content ?? (await project.read(path));\n const write = async (path: string, content: string, summary: readonly string[]) => {\n const draft = drafts.get(path);\n if (draft) {\n draft.content = content;\n draft.summary.push(...summary);\n return;\n }\n const exists = (await project.read(path)) !== undefined;\n drafts.set(path, {\n content,\n change: exists ? \"update\" : \"create\",\n summary: [...summary],\n });\n };\n const manifest = JSON.parse(\n (await project.read(\"package.json\")) ?? \"{}\",\n ) as PackageManifest;\n const fingerprint = () =>\n JSON.stringify([\n [...drafts].map(([path, draft]) => [path, draft.content]),\n dependencies,\n generate,\n ]);\n const listed = (name: string) =>\n Boolean(manifest.dependencies?.[name] ?? manifest.devDependencies?.[name]);\n\n for (const name of addons) {\n const addon: Addon = ADDONS[name];\n const before = fingerprint();\n\n const requirement = unmetRequirement(name, addon, layout);\n if (requirement) {\n manual.push(requirement);\n continue;\n }\n\n for (const payload of addon.files) {\n for (const path of await templates.list(payload.template, payload.dir)) {\n if ((await read(path)) === undefined) {\n await write(path, await templates.read(payload.template, path), []);\n }\n }\n }\n\n const edits = new Map<string, Op[]>();\n const addEdit = (file: string, ops: readonly Op[]) =>\n edits.set(file, [...(edits.get(file) ?? []), ...ops]);\n for (const fileEdit of addon.edits) {\n addEdit(\n fileEdit.file === SERVER_ENTRY ? layout.serverEntry : fileEdit.file,\n fileEdit.ops,\n );\n }\n for (const mount of addon.mounts) {\n if (await mounted(mount, project, drafts)) continue;\n addEdit(mount.into.file, [\n {\n kind: \"import\",\n names: [mount.component],\n from: relativeImport(mount.into.file, mount.module),\n },\n { kind: \"jsx-child\", component: mount.into.parent, element: mount.component },\n ]);\n }\n for (const [file, ops] of edits) {\n const text = await read(file);\n if (text === undefined) {\n manual.push({\n file,\n reason: `${file} is missing.`,\n change: ops.map((op) => manualCode(op)).join(\"\\n\\n\"),\n });\n continue;\n }\n const source = parseSource(file, text);\n const outcome = editSource(source, ops);\n if (outcome.status === \"done\") continue;\n if (outcome.status === \"unsafe\") {\n manual.push(manualFor(file, ops, outcome.unsafe));\n continue;\n }\n const summary = ops\n .filter((op) => evaluateOp(source, op).status === \"edit\")\n .map((op) => opSummary(source, op))\n .filter((line): line is string => line !== undefined);\n await write(file, outcome.text, summary);\n }\n\n if (addon.env.length > 0) {\n const current = (await read(ENV_EXAMPLE)) ?? \"\";\n const missing = addon.env.filter(\n (variable) => !new RegExp(`^${variable.name}=`, \"m\").test(current),\n );\n if (missing.length > 0) {\n const lines = missing.map(\n (variable) =>\n `# ${variable.description}\\n${variable.name}=${variable.example}\\n`,\n );\n const separator =\n current === \"\" || current.endsWith(\"\\n\\n\")\n ? \"\"\n : current.endsWith(\"\\n\")\n ? \"\\n\"\n : \"\\n\\n\";\n await write(\n ENV_EXAMPLE,\n `${current}${separator}${lines.join(\"\\n\")}`,\n missing.map((variable) => variable.name),\n );\n }\n }\n\n for (const [dependency, version] of Object.entries(addon.dependencies)) {\n if (dependencies.some((planned) => planned.name === dependency)) continue;\n if (listed(dependency)) continue;\n dependencies.push({\n name: dependency,\n version: await resolveVersion(version, project),\n dev: false,\n });\n }\n\n for (const generation of addon.generate) {\n if (!(await generationDone(layout, generation, project))) generate.push(generation);\n }\n\n if (fingerprint() !== before) notes.push(...addon.notes);\n }\n\n if (manual.length > 0) return { kind: \"blocked\", addons, manual };\n if (drafts.size === 0 && dependencies.length === 0 && generate.length === 0) {\n return { kind: \"done\", addons };\n }\n return {\n kind: \"ready\",\n addons,\n writes: [...drafts].map(([path, draft]) => ({ path, ...draft })),\n dependencies,\n generate,\n notes,\n };\n}\n\nconst ENV_EXAMPLE = \".env.example\";\n\nfunction unmetRequirement(\n name: AddonName,\n addon: Addon,\n layout: ProjectLayout,\n): ManualChange | undefined {\n const { requires } = addon;\n if (requires.serverAtRoot && layout.serverEntry.includes(\"/\")) {\n return {\n file: layout.serverEntry,\n reason: `${name} needs the server entry at the project root, and it is ${layout.serverEntry}.`,\n change: `Move ${layout.serverEntry} to server.ts and set server.entry to \"server.ts\" in parlor.config.ts.`,\n };\n }\n if (requires.schemaDir && layout.schemaDir !== requires.schemaDir) {\n return {\n file: \"parlor.config.ts\",\n reason: `${name} needs the schema in ${requires.schemaDir}/, and it is in ${layout.schemaDir}.`,\n change: `Set database.schema to \"${requires.schemaDir}\" in parlor.config.ts and move the tables there.`,\n };\n }\n const routes = `${dirname(layout.routeTree)}/routes`;\n if (requires.routesDir && routes !== requires.routesDir) {\n return {\n file: \"parlor.config.ts\",\n reason: `${name} needs the routes in ${requires.routesDir}/, and they are in ${routes}/.`,\n change: `Keep web.routeTree at its default so the routes live in ${requires.routesDir}/.`,\n };\n }\n return undefined;\n}\n\nasync function mounted(\n mount: Mount,\n project: ProjectFiles,\n drafts: ReadonlyMap<string, { readonly content: string }>,\n): Promise<boolean> {\n const paths = new Set([\n ...(await project.list(\"src\")),\n ...[...drafts.keys()].filter((path) => path.startsWith(\"src/\")),\n ]);\n for (const path of paths) {\n if (!isModule(path) || path === mount.module) continue;\n const text = drafts.get(path)?.content ?? (await project.read(path));\n if (text === undefined || !text.includes(mount.component)) continue;\n const source = parseSource(path, text);\n if (importedName(source, mount.component, relativeImport(path, mount.module)))\n return true;\n }\n return false;\n}\n\nfunction manualFor(\n file: string,\n ops: readonly Op[],\n failed: readonly UnsafeOp[],\n): ManualChange {\n const imports = ops.filter((op) => op.kind === \"import\").map((op) => manualCode(op));\n const changes = failed\n .filter((entry) => entry.op.kind !== \"import\")\n .map((entry) => manualCode(entry.op));\n return {\n file,\n reason: [...new Set(failed.map((entry) => entry.reason))].join(\" \"),\n change: [...(imports.length > 0 ? [imports.join(\"\\n\")] : []), ...changes].join(\n \"\\n\\n\",\n ),\n };\n}\n\nasync function resolveVersion(\n version: DependencyVersion,\n project: ProjectFiles,\n): Promise<string | FromPackage> {\n if (version === \"framework\") return FRAMEWORK_VERSION;\n if (typeof version === \"string\") return version;\n return (await resolveFromPackage(version, project)) ?? version;\n}\n\n/**\n * Whether a generation's declared outputs are in `project`: the route tree lists every route\n * id; the latest drizzle snapshot (the last entry of `meta/_journal.json`) has every table.\n */\nexport async function generationDone(\n layout: ProjectLayout,\n generation: Generation,\n project: ProjectFiles,\n): Promise<boolean> {\n if (generation.kind === \"route-tree\") {\n const tree = await project.read(layout.routeTree);\n return (\n tree !== undefined &&\n generation.routes.every((route) => tree.includes(`\"${route}\"`))\n );\n }\n const journal = await project.read(`${layout.migrationsDir}/meta/_journal.json`);\n if (journal === undefined) return false;\n const entries = (JSON.parse(journal) as { entries?: { idx: number }[] }).entries ?? [];\n const last = entries.at(-1);\n if (!last) return false;\n const snapshot = await project.read(\n `${layout.migrationsDir}/meta/${String(last.idx).padStart(4, \"0\")}_snapshot.json`,\n );\n if (snapshot === undefined) return false;\n const tables = Object.values(\n (JSON.parse(snapshot) as { tables?: Record<string, { name: string }> }).tables ?? {},\n ).map((table) => table.name);\n return generation.tables.every((table) => tables.includes(table));\n}\n"]}
@@ -0,0 +1,92 @@
1
+ import type { AddonName } from "../addons/index.js";
2
+ import { type PackageManager } from "../package-manager.js";
3
+ import type { ProjectLayout } from "./addon.js";
4
+ import { type AddPlan, type ManualChange } from "./plan.js";
5
+ /**
6
+ * The framework tools a run needs after files are staged. `parlor add` passes the same code
7
+ * `parlor typegen` and `parlor db generate` run, so this package stays free of the toolchain.
8
+ * Each takes the directory to run in, because the transaction runs them against the staging
9
+ * copy, never against the project.
10
+ */
11
+ export interface AddTools {
12
+ routeTree(rootDir: string): Promise<void>;
13
+ migration(rootDir: string, name: string): Promise<void>;
14
+ }
15
+ export interface AddRequest {
16
+ readonly layout: ProjectLayout;
17
+ readonly addons: readonly AddonName[];
18
+ readonly packageManager: PackageManager;
19
+ readonly tools: AddTools;
20
+ /** `dry-run` plans and reports without writing or installing. */
21
+ readonly mode: "apply" | "dry-run";
22
+ /** `false` (`--no-install`) writes files only and reports the install and generate commands. */
23
+ readonly install: boolean;
24
+ /** `false` (`--no-git-check`) skips the refusal on uncommitted changes. */
25
+ readonly gitCheck: boolean;
26
+ readonly log: (line: string) => void;
27
+ }
28
+ type ReadyPlan = Extract<AddPlan, {
29
+ kind: "ready";
30
+ }>;
31
+ export type AddResult = {
32
+ readonly kind: "done";
33
+ readonly addons: readonly AddonName[];
34
+ } | {
35
+ readonly kind: "blocked";
36
+ readonly addons: readonly AddonName[];
37
+ readonly manual: readonly ManualChange[];
38
+ } | {
39
+ readonly kind: "planned";
40
+ readonly plan: ReadyPlan;
41
+ }
42
+ /** The project has uncommitted changes and `gitCheck` is on: nothing was touched. */
43
+ | {
44
+ readonly kind: "dirty";
45
+ readonly addons: readonly AddonName[];
46
+ readonly files: readonly string[];
47
+ }
48
+ /** `pending` lists commands skipped by `--no-install`, to run before the next `parlor dev`. */
49
+ | {
50
+ readonly kind: "applied";
51
+ readonly plan: ReadyPlan;
52
+ /** Files the generators wrote, beyond the plan's own writes. */
53
+ readonly generated: readonly string[];
54
+ readonly pending: readonly string[];
55
+ }
56
+ /**
57
+ * Nothing reached the project: a failed install restored `package.json` and the lockfile; a
58
+ * failed or unvalidated generation discarded the staging copy and restored them too. Only
59
+ * `node_modules` may hold more than before, which is a cache, not state.
60
+ */
61
+ | {
62
+ readonly kind: "failed";
63
+ readonly addons: readonly AddonName[];
64
+ readonly stage: "install" | "generate";
65
+ readonly message: string;
66
+ };
67
+ /**
68
+ * The one entry point behind `parlor add`. A transaction in stages, so a failure never leaves
69
+ * an app that cannot boot and a crash is finished by the next run (every step it already did
70
+ * is `done`):
71
+ * 1. plan (no writes); `blocked`, `done` and `dry-run` return here
72
+ * 2. git: `uncommittedChanges(rootDir)` non-empty and `gitCheck` -> `dirty`
73
+ * 3. install: snapshot `package.json` and the lockfile; round one adds every dependency whose
74
+ * version is known (`<pm> add`), round two resolves each `FromPackage` from the manifest
75
+ * round one installed and adds those; any failure restores the snapshot -> `failed`
76
+ * 4. stage: `stageProject` copies the tree (minus node_modules, .git, .parlor) into
77
+ * `.parlor/add/<run id>/` and lays the planned writes over it
78
+ * 5. generate: each pending generation's tool runs in the staging dir; `generationDone`
79
+ * against the staging copy must then hold, else the copy is discarded -> `failed`
80
+ * 6. commit: every staged file whose bytes differ from the project is written through a temp
81
+ * file and a rename; the staging dir is removed
82
+ * `install: false` skips 3 and 5; `pending` then names the install commands and a second
83
+ * `parlor add` to generate.
84
+ */
85
+ export declare function addAddons(request: AddRequest): Promise<AddResult>;
86
+ /** The terminal text for a result and the exit code: 1 for `blocked`, `dirty` and `failed`, else 0. */
87
+ export declare function formatAddResult(result: AddResult, manager: PackageManager): {
88
+ readonly text: string;
89
+ readonly exitCode: 0 | 1;
90
+ };
91
+ export {};
92
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/add/run.ts"],"names":[],"mappings":"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"}